Compare commits

...

93 Commits
1.4.1 ... 1.6.0

Author SHA1 Message Date
Chris Collins
4fe5f87c43 Merge branch 'prerel-22-ui-settings-page' into 'main'
Prerel 22 ui settings page

See merge request personal/wedding-share!52
2025-04-25 20:43:48 +01:00
Chris Collins
6ca1b92b0b Prerel 22 ui settings page 2025-04-25 20:43:48 +01:00
Chris Collins
6a085ba387 Merge branch 'cherry-pick-87c9fece' into 'main'
Fix slideshow full screen glitch

See merge request personal/wedding-share!51
2025-04-22 21:21:02 +01:00
Chris Collins
3c55c84f8d Fix slideshow full screen glitch 2025-04-22 21:21:02 +01:00
Chris Collins
7fa0d97187 Merge branch 'prerel-1.5.11.3' into 'main'
Bug fix for slideshow update on full width gallery

See merge request personal/wedding-share!50
2025-04-22 19:43:33 +01:00
Cirx08
a592d42db1 Bug fix for slideshow update on full width gallery 2025-04-22 19:41:00 +01:00
Chris Collins
6f4eefe600 Merge branch 'prerel-1.5.11.2' into 'main'
Fixed export bug and custom resources linking

See merge request personal/wedding-share!49
2025-04-20 13:36:01 +01:00
Cirx08
6f667d96c2 Fixed export bug and custom resources linking 2025-04-20 13:30:37 +01:00
Chris Collins
8555aa5263 Merge branch 'prerel-1.5.12' into 'main'
Bug fix for slideshow

See merge request personal/wedding-share!48
2025-04-19 10:48:36 +00:00
Cirx08
5ae8050b55 Bug fix for slideshow 2025-04-19 11:32:40 +01:00
Chris Collins
4e2308e9b5 Merge branch 'prerel-11-track-file-and-gallery-size-in-db' into 'main'
Prerel 11 track file and gallery size in db

See merge request personal/wedding-share!47
2025-04-15 18:09:02 +00:00
Chris Collins
cc71b347fb Prerel 11 track file and gallery size in db 2025-04-15 18:09:02 +00:00
Cirx08
8478627095 Added option to hide default gallery in selector 2025-04-04 21:36:30 +01:00
Chris Collins
a4715789a2 Merge branch 'prerel-1.5.10' into 'main'
Prerel 1.5.10

See merge request personal/wedding-share!46
2025-04-04 15:52:26 +00:00
Chris Collins
66403abc01 Prerel 1.5.10 2025-04-04 15:52:26 +00:00
Chris Collins
ad3e072c1c Merge branch 'prerel-1.5.9' into 'main'
Prerel 1.5.9

See merge request personal/wedding-share!45
2025-03-25 19:27:33 +00:00
Chris Collins
30c8f83c9f Prerel 1.5.9 2025-03-25 19:27:33 +00:00
Chris Collins
3c6a4f722e Updated translations for Ukraine 2025-03-25 17:37:32 +00:00
Chris Collins
0f137755b5 Merge branch 'prerel-1.5.8' into 'main'
Prerel 1.5.8

See merge request personal/wedding-share!44
2025-03-21 09:14:30 +00:00
Chris Collins
1c9bc7fd5f Prerel 1.5.8 2025-03-21 09:14:30 +00:00
Chris
d632cb75a7 Bug fix for password generation 2025-03-06 16:09:14 +00:00
Chris Collins
68ab7f1696 Merge branch 'prerel-1.5.7.2' into 'main'
Prerel 1.5.7.2

See merge request personal/wedding-share!43
2025-03-06 14:12:27 +00:00
Chris Collins
c9380ec484 Prerel 1.5.7.2 2025-03-06 14:12:27 +00:00
Chris Collins
b8cb4a0116 Merge branch 'prerel-1.5.7.1' into 'main'
Bug fix for logo image

See merge request personal/wedding-share!42
2025-03-05 09:44:19 +00:00
Chris
f543365ab2 Bug fix for logo image 2025-03-05 09:42:23 +00:00
Chris Collins
eb77dee91c Merge branch 'prerel-1.5.7' into 'main'
Prerel 1.5.7

See merge request personal/wedding-share!41
2025-03-02 15:15:31 +00:00
Chris Collins
23f823d96f Prerel 1.5.7 2025-03-02 15:15:31 +00:00
Chris Collins
ae4a1abdbe Merge branch 'prerel-1.5.6' into 'main'
v1.5.6

See merge request personal/wedding-share!40
2025-03-02 11:21:04 +00:00
Chris Collins
c998435312 v1.5.6 2025-03-02 11:21:04 +00:00
Chris Collins
9d58df9b89 Fix for email regex 2025-03-01 23:50:03 +00:00
Cirx08
4f0b299923 Updated readme 2025-03-01 12:58:36 +00:00
Chris Collins
8719820f69 Merge branch 'prerel-1.5.5' into 'main'
Prerel 1.5.5

See merge request personal/wedding-share!39
2025-03-01 12:52:57 +00:00
Chris Collins
2e103cbdee Prerel 1.5.5 2025-03-01 12:52:56 +00:00
Cirx08
8443f59c4e Fixed translation issue 2025-02-26 20:49:28 +00:00
Cirx08
79ff66093f Added translation request ticket type 2025-02-23 19:05:45 +00:00
Cirx08
1190798093 Updated German translations 2025-02-23 16:31:33 +00:00
Chris
3a245b695b Bug fix for loader not at top of screen 2025-02-21 12:41:07 +00:00
Chris
2c529ae117 Remove .ToLower() from lang selector 2025-02-21 12:17:09 +00:00
Chris Collins
1396e85a3e Merge branch 'prerel-1.5.4' into 'main'
Prerel 1.5.4

See merge request personal/wedding-share!38
2025-02-21 12:03:19 +00:00
Chris Collins
fa8cc19750 Prerel 1.5.4 2025-02-21 12:03:18 +00:00
Chris Collins
d5f0a7601f Merge branch 'prerel-1.5.3.1' into 'main'
Prerel 1.5.3.1

See merge request personal/wedding-share!37
2025-02-19 14:20:57 +00:00
Chris Collins
f96b2c827c Prerel 1.5.3.1 2025-02-19 14:20:57 +00:00
Chris Collins
15da9dbc08 Merge branch 'prerel-1.5.3' into 'main'
Prerel 1.5.3

See merge request personal/wedding-share!36
2025-02-18 13:32:51 +00:00
Chris Collins
10253c7021 Prerel 1.5.3 2025-02-18 13:32:51 +00:00
Cirx08
30eb4cd66d Bug fix for Sweedish JS translations 2025-02-12 19:31:01 +00:00
Chris
200d3ba26c Updated DBUp 2025-02-10 09:35:41 +00:00
Chris Collins
1ece55d6a0 Merge branch 'prerel-1.5.1' into 'main'
Prerel 1.5.1

See merge request personal/wedding-share!35
2025-02-09 21:46:59 +00:00
Chris Collins
a63019fd2d Prerel 1.5.1 2025-02-09 21:46:59 +00:00
Chris
5b6987227a Bumped version number as this is a major release version 2025-02-07 16:44:47 +00:00
Chris Collins
6447e5eb79 Merge branch 'prerel-1.4.16' into 'main'
Prerel 1.4.16

See merge request personal/wedding-share!34
2025-02-07 16:35:48 +00:00
Chris Collins
5bd6de3875 Prerel 1.4.16 2025-02-07 16:35:48 +00:00
Chris Collins
a1dc295b68 Merge branch 'prerel-1.4.15' into 'main'
Prerel 1.4.15

See merge request personal/wedding-share!33
2025-02-02 12:23:46 +00:00
Chris Collins
dd390280fb Prerel 1.4.15 2025-02-02 12:23:46 +00:00
Cirx08
ce510d8d21 Added issue templates 2025-02-01 10:07:08 +00:00
Chris Collins
d1b7867b37 Merge branch 'prerel-1.4.14' into 'main'
Prerel 1.4.14

See merge request personal/wedding-share!32
2025-02-01 00:52:36 +00:00
Chris Collins
8498414fb0 Prerel 1.4.14 2025-02-01 00:52:36 +00:00
Chris Collins
e2162300f0 Merge branch 'prerel-1.4.13.2' into 'main'
Edit media-viewer.js

See merge request personal/wedding-share!31
2025-01-30 23:16:11 +00:00
Chris Collins
d4512eef97 Edit media-viewer.js 2025-01-30 23:16:11 +00:00
Chris Collins
cb71b09aad Merge branch 'prerel-1.4.13' into 'main'
Prerel 1.4.13

See merge request personal/wedding-share!30
2025-01-30 21:41:04 +00:00
Chris Collins
d982c64aa7 Prerel 1.4.13 2025-01-30 21:41:04 +00:00
Cirx08
5a541b78f1 Updated pipeline 2025-01-26 21:29:43 +00:00
Cirx08
7f8692ee29 Updated pipeline 2025-01-26 21:23:37 +00:00
Cirx08
716bb0dbce Updated pipeline 2025-01-26 20:56:02 +00:00
Chris Collins
a5414ed424 Merge branch 'prerel-1.4.12' into 'main'
Prerel 1.4.12

See merge request personal/wedding-share!29
2025-01-26 20:49:52 +00:00
Chris Collins
67e0d0c8a5 Prerel 1.4.12 2025-01-26 20:49:52 +00:00
Chris Collins
85caccc899 Merge branch 'prerel-1.4.11' into 'main'
Prerel 1.4.11

See merge request personal/wedding-share!28
2025-01-19 17:18:17 +00:00
Chris Collins
060cccdcf9 Prerel 1.4.11 2025-01-19 17:18:16 +00:00
Cirx08
abbd6ceb48 Updated pipeline trigger 2025-01-13 22:23:17 +00:00
Cirx08
f02e6b8e5a Updated pipeline 2025-01-13 22:18:44 +00:00
Chris Collins
a30f602b65 Merge branch '1.4.10' into 'main'
1.4.10

See merge request personal/wedding-share!27
2025-01-13 22:12:46 +00:00
Chris Collins
06e20737d0 1.4.10 2025-01-13 22:12:46 +00:00
Chris Collins
bd335509ff Merge branch 'prerel-1.4.9' into 'main'
Prerel 1.4.9

See merge request personal/wedding-share!26
2025-01-11 21:26:06 +00:00
Chris Collins
6ad6662e01 Prerel 1.4.9 2025-01-11 21:26:06 +00:00
Chris Collins
a632cf3a2f Merge branch 'prerel-1.4.8' into 'main'
Prerel 1.4.8

See merge request personal/wedding-share!25
2025-01-09 22:09:34 +00:00
Chris Collins
0e09b8db39 Prerel 1.4.8 2025-01-09 22:09:34 +00:00
Chris Collins
04ef6d63bb Merge branch 'prerel-1.4.7' into 'main'
Prerel 1.4.7

See merge request personal/wedding-share!24
2025-01-07 20:55:49 +00:00
Chris Collins
aa5d1af299 Prerel 1.4.7 2025-01-07 20:55:49 +00:00
Chris Collins
c4ab287ba7 Update readme.md 2025-01-07 09:41:33 +00:00
Chris Collins
5a16c6f800 Update readme.md 2025-01-07 09:36:49 +00:00
Cirx08
a3a74d23c4 Fixed bug to prevent default title leaking to the homepage 2025-01-06 20:11:16 +00:00
Chris Collins
6a05fadd45 Merge branch 'prerel-1.4.6' into 'main'
Prerel 1.4.6

See merge request personal/wedding-share!23
2025-01-06 18:59:15 +00:00
Chris Collins
e182597d8f Prerel 1.4.6 2025-01-06 18:59:15 +00:00
Chris Collins
4bc22a94eb Merge branch 'prerel-1.4.5' into 'main'
Prerel 1.4.5

See merge request personal/wedding-share!22
2024-12-28 13:15:31 +00:00
Chris Collins
a5afe90267 Prerel 1.4.5 2024-12-28 13:15:31 +00:00
Chris Collins
a3e1e62082 Merge branch 'prerel-1.4.4' into 'main'
Added gallery specific overrides

See merge request personal/wedding-share!21
2024-12-26 18:02:51 +00:00
Cirx08
b90d24e5de Added gallery specific overrides 2024-12-26 17:57:00 +00:00
Cirx08
39fe7b58a4 Updated screenshots and added dark mode screenshots to readme 2024-12-24 16:30:57 +00:00
Cirx08
217a0294ac Added support message to readme 2024-12-24 15:58:10 +00:00
Cirx08
2c21d76245 Bumped version number 2024-12-23 21:53:44 +00:00
Cirx08
83de634df1 Changed to both bulk approve and reject 2024-12-23 21:53:04 +00:00
Chris Collins
7861eeeb07 Merge branch 'prerel-1.4.2' into 'main'
Prerel 1.4.2

See merge request personal/wedding-share!20
2024-12-23 21:32:36 +00:00
Chris Collins
9f5baf2aa1 Prerel 1.4.2 2024-12-23 21:32:36 +00:00
Cirx08
8de5223dfc Moved account settings in readme 2024-12-23 12:29:04 +00:00
721 changed files with 269928 additions and 5331 deletions

39
.github/ISSUE_TEMPLATE/bug-report.md vendored Normal file
View File

@@ -0,0 +1,39 @@
---
name: Bug Report
about: Create a report to help us improve
title: "[Bug] - "
labels: bug
assignees: ''
---
**Description:**
A clear and concise description of what the bug is.
**Expected Behaviour:**
A clear and concise description of what you expected to happen.
**Reproduction Steps:**
Steps to reproduce the behaviour:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Cache Cleared:**
If the issue is a front end issue please confirm you have first attempted to clear the cache and the issue still persists.
**Docker Compose:**
If possible please provide your compose setup or a list of environment variables to help debug the issue.
**Device:**
Pleases tell me some more details about the device you are having the issue on. Is the issue isolated to the device or can you reproduce on multiple devices?
- Desktop: [e.g. 1080p Chrome, 2K Safari, 4K Brave]
- Mobile [e.g. iPhone 16, Samsung S24 Ultra]
**Screenshots:**
If applicable, add screenshots to help explain your problem.
**Additional Details:**
Add any other context about the problem here.

View File

@@ -0,0 +1,17 @@
---
name: Feature Request
about: Suggest an idea or enhancement for this project
title: "[Feature] "
labels: enhancement, feature
assignees: ''
---
**Description:**
A clear and concise description of what you want to happen.
**Examples:**
A clear and concise description of any alternative solutions you've used that have similar functionality.
**Additional Details:**
Add any other context or screenshots about the feature request here.

View File

@@ -0,0 +1,14 @@
---
name: Translation Request
about: Create a report to help us improve
title: "[Translations] - "
labels: translations
assignees: ''
---
**Description:**
A clear and concise description of the changes being made.
**Pull Request:**
A link to the pull request containing the updates to BOTH languages resource as specified by the documentation - `https://docs.wedding-share.org/docs/languages#addingupdating-languages`.

5
.gitignore vendored
View File

@@ -19,3 +19,8 @@
/WeddingShare/wedding-share.db
/WeddingShare/config/wedding-share.db
/WeddingShare/wwwroot/thumbnails
/WeddingShare/wwwroot/temp
/WeddingShare/ffmpeg
/WeddingShare/wwwroot/logos
/WeddingShare/wwwroot/banners
/WeddingShare/wwwroot/images/custom_resources

View File

@@ -2,12 +2,14 @@ image: mcr.microsoft.com/dotnet/sdk:latest
stages:
- test
- build
- push
- release
variables:
BUILD_DOCKERFILE: 'WeddingShare/Dockerfile'
BUILD_CONFIGURATION: 'Release'
BUILD_PLATFORMS: 'linux/arm/v7,linux/arm64/v8,linux/amd64'
BUILD_BUILDER_NAME: 'wedding-share-builder'
OBJECTS_DIRECTORY: 'obj'
NUGET_PACKAGES_DIRECTORY: '.nuget'
SOURCE_CODE_PATH: '*/*/'
@@ -22,36 +24,25 @@ cache:
before_script:
- 'docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY'
# - 'docker buildx rm --all-inactive --force'
# - 'docker buildx create --name $BUILD_BUILDER_NAME --driver=docker-container'
test:
stage: test
script:
- 'dotnet restore'
- 'dotnet test'
build:
stage: build
script:
- 'docker build -t $CI_REGISTRY_IMAGE:dev -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA -f WeddingShare/Dockerfile .'
- 'docker push $CI_REGISTRY_IMAGE:dev'
- 'docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA'
needs:
- test
push_pre_release:
variables:
GIT_STRATEGY: none
stage: push
only:
- /^(prerel|rc|release)-[0-9]+\.[0-9]+\.[0-9]+$/
- /^(prerel|rc|release)-[0-9]+.+/
script:
- 'docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA'
- 'docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH'
- 'docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH'
- 'docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:pre_release'
- 'docker push $CI_REGISTRY_IMAGE:pre_release'
- 'docker buildx build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH --tag $CI_REGISTRY_IMAGE:pre_release --platform $BUILD_PLATFORMS --builder $BUILD_BUILDER_NAME --push -f $BUILD_DOCKERFILE .'
needs:
- build
- test
push_latest:
variables:
@@ -61,11 +52,9 @@ push_latest:
- main
- master
script:
- 'docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA'
- 'docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:latest'
- 'docker push $CI_REGISTRY_IMAGE:latest'
- 'docker buildx build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_BRANCH --tag $CI_REGISTRY_IMAGE:latest --platform $BUILD_PLATFORMS --builder $BUILD_BUILDER_NAME --push -f $BUILD_DOCKERFILE .'
needs:
- build
- test
push_tag:
variables:
@@ -74,24 +63,18 @@ push_tag:
only:
- tags
script:
- 'docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA'
- 'docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME'
- 'docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME'
- 'docker buildx build --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME --platform $BUILD_PLATFORMS --builder $BUILD_BUILDER_NAME --push -f $BUILD_DOCKERFILE .'
needs:
- build
- test
push_docker_hub:
variables:
GIT_STRATEGY: none
stage: push
stage: release
only:
- tags
script:
- 'docker pull $CI_REGISTRY_IMAGE:latest'
- 'docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_TOKEN'
- 'docker tag $CI_REGISTRY_IMAGE:latest $DOCKERHUB_USERNAME/wedding_share:latest'
- 'docker push $DOCKERHUB_USERNAME/wedding_share:latest'
- 'docker tag $CI_REGISTRY_IMAGE:latest $DOCKERHUB_USERNAME/wedding_share:$CI_COMMIT_REF_NAME'
- 'docker push $DOCKERHUB_USERNAME/wedding_share:$CI_COMMIT_REF_NAME'
- 'docker buildx build --tag $DOCKERHUB_USERNAME/wedding_share:$CI_COMMIT_REF_NAME --tag $DOCKERHUB_USERNAME/wedding_share:latest --platform $BUILD_PLATFORMS --builder $BUILD_BUILDER_NAME --push -f $BUILD_DOCKERFILE .'
needs:
- build
- push_tag

View File

@@ -7,19 +7,19 @@ namespace WeddingShare.UnitTests.Helpers
{
internal class MockData
{
public static DefaultHttpContext MockHttpContext(Dictionary<string, StringValues>? form = null, IFormFileCollection? files = null)
{
var ctx = new DefaultHttpContext()
{
Session = new MockSession()
};
public static DefaultHttpContext MockHttpContext(Dictionary<string, StringValues>? form = null, IFormFileCollection? files = null, MockSession session = null)
{
var ctx = new DefaultHttpContext()
{
Session = session ?? new MockSession()
};
ctx.Request.Form = new FormCollection(form, files);
ctx.Request.Form = new FormCollection(form, files);
return ctx;
}
return ctx;
}
public static List<GalleryItemModel> MockGalleryItems(int count = 10, int? galleryId = null, GalleryItemState state = GalleryItemState.All)
public static List<GalleryItemModel> MockGalleryItems(int count = 10, int? galleryId = null, GalleryItemState state = GalleryItemState.All)
{
var result = new List<GalleryItemModel>();
@@ -41,8 +41,10 @@ namespace WeddingShare.UnitTests.Helpers
GalleryId = galleryId != null ? (int)galleryId : rand.Next(),
Title = $"{Guid.NewGuid()}.{MockFileExtension()}",
UploadedBy = rand.Next(2) % 2 == 0 ? Guid.NewGuid().ToString() : null,
MediaType = (MediaType)rand.Next(3),
State = state == GalleryItemState.All ? (GalleryItemState)rand.Next(2) : state,
};
FileSize = (int)rand.Next(2),
};
}
public static string MockFileExtension()

View File

@@ -34,6 +34,11 @@ namespace WeddingShare.UnitTests.Helpers
this.Values.Remove(key);
}
public void Set(string key, string value)
{
this.Set(key, Encoding.UTF8.GetBytes(value));
}
public void Set(string key, byte[] value)
{
if (this.Values.ContainsKey(key))

View File

@@ -4,8 +4,8 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using System.Net;
using System.Text.Json;
using NSubstitute.ReturnsExtensions;
using WeddingShare.Constants;
using WeddingShare.Controllers;
using WeddingShare.Enums;
using WeddingShare.Helpers;
@@ -20,15 +20,16 @@ namespace WeddingShare.UnitTests.Tests.Helpers
public class GalleryControllerTests
{
private readonly IWebHostEnvironment _env = Substitute.For<IWebHostEnvironment>();
private readonly IConfigHelper _config = Substitute.For<IConfigHelper>();
private readonly ISecretKeyHelper _secretKey = Substitute.For<ISecretKeyHelper>();
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
private readonly IDatabaseHelper _database = Substitute.For<IDatabaseHelper>();
private readonly IFileHelper _file = Substitute.For<IFileHelper>();
private readonly IDeviceDetector _deviceDetector = Substitute.For<IDeviceDetector>();
private readonly IImageHelper _image = Substitute.For<IImageHelper>();
private readonly INotificationHelper _notification = Substitute.For<INotificationHelper>();
private readonly IEncryptionHelper _encryption = Substitute.For<IEncryptionHelper>();
private readonly WeddingShare.Helpers.IUrlHelper _url = Substitute.For<WeddingShare.Helpers.IUrlHelper>();
private readonly ILogger<GalleryController> _logger = Substitute.For<ILogger<GalleryController>>();
private readonly IStringLocalizer<GalleryController> _localizer = Substitute.For<IStringLocalizer<GalleryController>>();
private readonly IStringLocalizer<Lang.Translations> _localizer = Substitute.For<IStringLocalizer<Lang.Translations>>();
public GalleryControllerTests()
{
@@ -39,7 +40,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
_env.WebRootPath.Returns("/app/wwwroot");
_database.GetGallery("default").Returns(Task.FromResult<GalleryModel?>(new GalleryModel()
_database.GetGallery("default").Returns(Task.FromResult<GalleryModel?>(new GalleryModel()
{
Id = 1,
Name = "default",
@@ -48,6 +49,15 @@ namespace WeddingShare.UnitTests.Tests.Helpers
PendingItems = 50,
TotalItems = 72
}));
_database.GetGallery("blaa").Returns(Task.FromResult<GalleryModel?>(new GalleryModel()
{
Id = 2,
Name = "blaa",
SecretKey = "456789",
ApprovedItems = 2,
PendingItems = 1,
TotalItems = 3
}));
_database.GetGallery("missing").Returns(Task.FromResult<GalleryModel?>(null));
_database.AddGallery(Arg.Any<GalleryModel>()).Returns(Task.FromResult<GalleryModel?>(new GalleryModel()
{
@@ -60,67 +70,122 @@ namespace WeddingShare.UnitTests.Tests.Helpers
}));
_database.AddGalleryItem(Arg.Any<GalleryItemModel>()).Returns(Task.FromResult<GalleryItemModel?>(MockData.MockGalleryItem()));
_database.GetAllGalleryItems(Arg.Any<int>(), GalleryItemState.All).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.All)));
_database.GetAllGalleryItems(Arg.Any<int>(), GalleryItemState.Pending).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.Pending)));
_database.GetAllGalleryItems(Arg.Any<int>(), GalleryItemState.Approved).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.Approved)));
_secretKey.GetGallerySecretKey(Arg.Any<string>()).Returns("password");
_secretKey.GetGallerySecretKey("missing").Returns("123456");
_database.GetAllGalleryItems(Arg.Any<int>(), GalleryItemState.All, Arg.Any<MediaType>(), Arg.Any<ImageOrientation>(), Arg.Any<GalleryGroup>(), Arg.Any<GalleryOrder>(), Arg.Any<int>(), Arg.Any<int>()).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.All)));
_database.GetAllGalleryItems(Arg.Any<int>(), GalleryItemState.Pending, Arg.Any<MediaType>(), Arg.Any<ImageOrientation>(), Arg.Any<GalleryGroup>(), Arg.Any<GalleryOrder>(), Arg.Any<int>(), Arg.Any<int>()).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.Pending)));
_database.GetAllGalleryItems(Arg.Any<int>(), GalleryItemState.Approved, Arg.Any<MediaType>(), Arg.Any<ImageOrientation>(), Arg.Any<GalleryGroup>(), Arg.Any<GalleryOrder>(), Arg.Any<int>(), Arg.Any<int>()).Returns(Task.FromResult(MockData.MockGalleryItems(10, 1, GalleryItemState.Approved)));
_database.GetGalleryItemByChecksum(Arg.Any<int>(), Arg.Any<string>()).ReturnsNull();
_config.GetOrDefault("Settings", "Allowed_File_Types", Arg.Any<string>()).Returns(".jpg,.jpeg,.png");
_config.GetOrDefault("Settings", "Default_Gallery_View", Arg.Any<int>()).Returns((int)ViewMode.Default);
_config.GetOrDefault("Settings", "Require_Review", Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Settings", "Max_File_Size_Mb", Arg.Any<int>()).Returns(10);
_settings.GetOrDefault(Settings.Gallery.SecretKey, Arg.Any<string>(), Arg.Any<string>()).Returns("password");
_settings.GetOrDefault(Settings.Gallery.SecretKey, Arg.Any<string>(), "blaa").Returns("456789");
_settings.GetOrDefault(Settings.Gallery.SecretKey, Arg.Any<string>(), "missing").Returns("123456");
_settings.GetOrDefault(Settings.Gallery.Upload, Arg.Any<bool>(), Arg.Any<string>()).Returns(true);
_settings.GetOrDefault(Settings.Gallery.Download, Arg.Any<bool>(), Arg.Any<string>()).Returns(true);
_settings.GetOrDefault(Settings.Gallery.UploadPeriod, Arg.Any<string>(), Arg.Any<string>()).Returns("1970-01-01 00:00:00");
_settings.GetOrDefault(Settings.Gallery.PreventDuplicates, Arg.Any<bool>(), Arg.Any<string>()).Returns(true);
_settings.GetOrDefault(Settings.Gallery.DefaultView, Arg.Any<int>(), Arg.Any<string>()).Returns((int)ViewMode.Default);
_settings.GetOrDefault(Settings.Gallery.AllowedFileTypes, Arg.Any<string>(), Arg.Any<string>()).Returns(".jpg,.jpeg,.png,.mp4,.mov");
_settings.GetOrDefault(Settings.Gallery.RequireReview, Arg.Any<bool>(), Arg.Any<string>()).Returns(true);
_settings.GetOrDefault(Settings.Gallery.MaxFileSizeMB, Arg.Any<int>(), Arg.Any<string>()).Returns(10);
_file.GetChecksum(Arg.Any<string>()).Returns(Guid.NewGuid().ToString());
_notification.Send(Arg.Any<string>(), Arg.Any<string>()).Returns(Task.FromResult(true));
_localizer[Arg.Any<string>()].Returns(new LocalizedString("UnitTest", "UnitTest"));
}
[TestCase(DeviceType.Desktop, "default", "password", ViewMode.Default, GalleryOrder.None)]
[TestCase(DeviceType.Mobile, "blaa", "123456", ViewMode.Presentation, GalleryOrder.UploadedAsc)]
[TestCase(DeviceType.Tablet, "missing", "123456", ViewMode.Slideshow, GalleryOrder.NameAsc)]
public async Task GalleryController_Index(DeviceType deviceType, string id, string? key, ViewMode? mode, GalleryOrder order)
[TestCase(DeviceType.Desktop, 1, "default", "password", ViewMode.Default, GalleryGroup.None, GalleryOrder.Descending, true)]
[TestCase(DeviceType.Mobile, 2, "blaa", "456789", ViewMode.Presentation, GalleryGroup.Date, GalleryOrder.Ascending, true)]
[TestCase(DeviceType.Tablet, 101, "missing", "123456", ViewMode.Slideshow, GalleryGroup.Uploader, GalleryOrder.Ascending, false)]
public async Task GalleryController_Index(DeviceType deviceType, int id, string name, string? key, ViewMode? mode, GalleryGroup group, GalleryOrder order, bool existing)
{
_deviceDetector.ParseDeviceType(Arg.Any<string>()).Returns(deviceType);
_config.GetOrDefault("Settings", "Single_Gallery_Mode", Arg.Any<bool>()).Returns(false);
_settings.GetOrDefault(Settings.Basic.SingleGalleryMode, Arg.Any<bool>()).Returns(false);
_settings.GetOrDefault(Settings.Basic.GuestGalleryCreation, Arg.Any<bool>()).Returns(false);
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext();
ViewResult actual = (ViewResult)await controller.Index(id, key, mode, order);
if (existing)
{
ViewResult actual = (ViewResult)await controller.Index(name, key, mode, group, order);
Assert.That(actual, Is.TypeOf<ViewResult>());
Assert.That(actual?.Model, Is.Not.Null);
PhotoGallery model = (PhotoGallery)actual.Model;
Assert.That(model?.GalleryId, Is.EqualTo(id));
Assert.That(model?.GalleryName, Is.EqualTo(name));
Assert.That(model.ViewMode, Is.EqualTo(mode));
Assert.That(model?.FileUploader?.GalleryId, Is.EqualTo(name));
Assert.That(model?.FileUploader?.SecretKey, Is.EqualTo(key));
Assert.That(model?.FileUploader?.UploadUrl, Is.EqualTo("/Gallery/UploadImage"));
}
else
{
RedirectToActionResult actual = (RedirectToActionResult)await controller.Index(name, key, mode, group, order);
Assert.That(actual, Is.TypeOf<RedirectToActionResult>());
}
}
[TestCase(true, true)]
[TestCase(false, false)]
public async Task GalleryController_UploadDisabled(bool disabled, bool expected)
{
_deviceDetector.ParseDeviceType(Arg.Any<string>()).Returns(DeviceType.Desktop);
_settings.GetOrDefault(Settings.Basic.SingleGalleryMode, Arg.Any<bool>()).Returns(false);
_settings.GetOrDefault(Settings.Gallery.Upload, Arg.Any<bool>(), Arg.Any<string>()).Returns(disabled);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext();
ViewResult actual = (ViewResult)await controller.Index("default", "password", ViewMode.Default, GalleryGroup.None, GalleryOrder.Descending);
Assert.That(actual, Is.TypeOf<ViewResult>());
Assert.That(actual?.Model, Is.Not.Null);
PhotoGallery model = (PhotoGallery)actual.Model;
Assert.That(model?.GalleryId, Is.EqualTo(id));
Assert.That(model.GalleryPath, Is.EqualTo($"/uploads/{id}"));
Assert.That(model.ThumbnailsPath, Is.EqualTo($"/thumbnails"));
Assert.That(model.ViewMode, Is.EqualTo(mode));
Assert.That(model?.FileUploader?.GalleryId, Is.EqualTo(id));
Assert.That(model?.FileUploader?.SecretKey, Is.EqualTo(key));
Assert.That(model?.FileUploader?.UploadUrl, Is.EqualTo("/Gallery/UploadImage"));
Assert.That(model?.FileUploader, expected ? Is.Not.Null : Is.Null);
}
[TestCase(DeviceType.Desktop, ViewMode.Default, GalleryOrder.None)]
[TestCase(DeviceType.Mobile, ViewMode.Presentation, GalleryOrder.UploadedAsc)]
[TestCase(DeviceType.Tablet, ViewMode.Slideshow, GalleryOrder.NameAsc)]
public async Task GalleryController_Index_SingleGalleryMode(DeviceType deviceType, ViewMode? mode, GalleryOrder order)
[TestCase("1970-01-01 00:00", true)]
[TestCase("3000-01-01 00:00", false)]
[TestCase("1970-01-01 00:00 / 1980-01-01 00:00", false)]
[TestCase("2999-01-01 00:00 / 3000-01-01 00:00", false)]
[TestCase("1970-01-01 00:00 / 3000-01-01 00:00", true)]
public async Task GalleryController_UploadDisabled(string uploadPeriod, bool expected)
{
_deviceDetector.ParseDeviceType(Arg.Any<string>()).Returns(DeviceType.Desktop);
_settings.GetOrDefault(Settings.Basic.SingleGalleryMode, Arg.Any<bool>()).Returns(false);
_settings.GetOrDefault(Settings.Gallery.UploadPeriod, Arg.Any<string>(), Arg.Any<string>()).Returns(uploadPeriod);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext();
ViewResult actual = (ViewResult)await controller.Index("default", "password", ViewMode.Default, GalleryGroup.None, GalleryOrder.Descending);
Assert.That(actual, Is.TypeOf<ViewResult>());
Assert.That(actual?.Model, Is.Not.Null);
PhotoGallery model = (PhotoGallery)actual.Model;
Assert.That(model?.FileUploader, expected ? Is.Not.Null : Is.Null);
}
[TestCase(DeviceType.Desktop, ViewMode.Default, GalleryGroup.None, GalleryOrder.Descending)]
[TestCase(DeviceType.Mobile, ViewMode.Presentation, GalleryGroup.Date, GalleryOrder.Ascending)]
[TestCase(DeviceType.Tablet, ViewMode.Slideshow, GalleryGroup.Uploader, GalleryOrder.Ascending)]
public async Task GalleryController_Index_SingleGalleryMode(DeviceType deviceType, ViewMode? mode, GalleryGroup group, GalleryOrder order)
{
_deviceDetector.ParseDeviceType(Arg.Any<string>()).Returns(deviceType);
_config.GetOrDefault("Settings", "Single_Gallery_Mode", Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Settings.Basic.SingleGalleryMode, Arg.Any<bool>()).Returns(true);
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext();
ViewResult actual = (ViewResult)await controller.Index("default", "password", mode, order);
ViewResult actual = (ViewResult)await controller.Index("default", "password", mode, group, order);
Assert.That(actual, Is.TypeOf<ViewResult>());
Assert.That(actual?.Model, Is.Not.Null);
PhotoGallery model = (PhotoGallery)actual.Model;
Assert.That(model?.GalleryId, Is.EqualTo("default"));
Assert.That(model.GalleryPath, Is.EqualTo($"/uploads/default"));
Assert.That(model.ThumbnailsPath, Is.EqualTo($"/thumbnails"));
Assert.That(model?.GalleryId, Is.EqualTo(1));
Assert.That(model?.GalleryName, Is.EqualTo("default"));
Assert.That(model.ViewMode, Is.EqualTo(mode));
Assert.That(model?.FileUploader?.GalleryId, Is.EqualTo("default"));
Assert.That(model?.FileUploader?.SecretKey, Is.EqualTo("password"));
@@ -133,7 +198,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(false, 3, "Unit Testing")]
public async Task GalleryController_UploadImage(bool requiresReview, int fileCount, string? uploadedBy)
{
_config.GetOrDefault("Settings", "Require_Review", Arg.Any<bool>()).Returns(requiresReview);
_settings.GetOrDefault(Settings.Gallery.RequireReview, Arg.Any<bool>()).Returns(requiresReview);
var files = new FormFileCollection();
for (var i = 0; i < fileCount; i++)
@@ -141,13 +206,16 @@ namespace WeddingShare.UnitTests.Tests.Helpers
files.Add(new FormFile(null, 0, 0, "TestFile_001", $"{Guid.NewGuid()}.jpg"));
}
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var session = new MockSession();
session.Set(SessionKey.ViewerIdentity, uploadedBy ?? string.Empty);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(
session: session,
form: new Dictionary<string, StringValues>
{
{ "Id", "default" },
{ "SecretKey", "password" },
{ "UploadedBy", uploadedBy }
{ "SecretKey", "password" }
},
files: files);
@@ -160,11 +228,40 @@ namespace WeddingShare.UnitTests.Tests.Helpers
Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List<string>()).Count, Is.EqualTo(0));
}
[TestCase(null)]
[TestCase]
public async Task GalleryController_UploadImage_Duplicate()
{
_database.GetGalleryItemByChecksum(Arg.Any<int>(), Arg.Any<string>()).Returns(Task.FromResult(MockData.MockGalleryItems(1, 1, GalleryItemState.Approved).FirstOrDefault()));
var files = new FormFileCollection();
files.Add(new FormFile(null, 0, 0, "TestFile_001", $"{Guid.NewGuid()}.jpg"));
var session = new MockSession();
session.Set(SessionKey.ViewerIdentity, string.Empty);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(
session: session,
form: new Dictionary<string, StringValues>
{
{ "Id", "default" },
{ "SecretKey", "password" }
},
files: files);
JsonResult actual = (JsonResult)await controller.UploadImage();
Assert.That(actual, Is.TypeOf<JsonResult>());
Assert.That(actual?.Value, Is.Not.Null);
Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "success", false), Is.False);
Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "uploaded", 0), Is.EqualTo(0));
Assert.That(JsonResponseHelper.GetPropertyValue(actual.Value, "errors", new List<string>()).Count, Is.GreaterThan(0));
}
[TestCase(null)]
[TestCase("")]
public async Task GalleryController_UploadImage_InvalidGallery(string? id)
{
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary<string, StringValues>
{
{ "Id", id }
@@ -182,7 +279,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("")]
public async Task GalleryController_UploadImage_InvalidSecretKey(string? key)
{
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary<string, StringValues>
{
{ "Id", "default" },
@@ -200,7 +297,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase()]
public async Task GalleryController_UploadImage_MissingGallery()
{
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary<string, StringValues>
{
{ "Id", Guid.NewGuid().ToString() }
@@ -217,7 +314,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase()]
public async Task GalleryController_UploadImage_NoFiles()
{
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(form: new Dictionary<string, StringValues>
{
{ "Id", "default" },
@@ -235,7 +332,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase()]
public async Task GalleryController_UploadImage_FileTooBig()
{
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(
form: new Dictionary<string, StringValues>
{
@@ -257,7 +354,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase()]
public async Task GalleryController_UploadImage_InvalidFileType()
{
var controller = new GalleryController(_env, _config, _database, _file, _secretKey, _deviceDetector, _image, _notification, _logger, _localizer);
var controller = new GalleryController(_env, _settings, _database, _file, _deviceDetector, _image, _notification, _encryption, _url, _logger, _localizer);
controller.ControllerContext.HttpContext = MockData.MockHttpContext(
form: new Dictionary<string, StringValues>
{

View File

@@ -1,19 +1,23 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using WeddingShare.Constants;
using WeddingShare.Controllers;
using WeddingShare.Enums;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.UnitTests.Helpers;
namespace WeddingShare.UnitTests.Tests.Helpers
{
public class HomeControllerTests
{
private readonly IConfigHelper _config = Substitute.For<IConfigHelper>();
private readonly ISecretKeyHelper _secretKey = Substitute.For<ISecretKeyHelper>();
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
private readonly IDatabaseHelper _database = Substitute.For<IDatabaseHelper>();
private readonly IDeviceDetector _deviceDetector = Substitute.For<IDeviceDetector>();
private readonly ILogger<HomeController> _logger = Substitute.For<ILogger<HomeController>>();
private readonly IStringLocalizer<Lang.Translations> _localizer = Substitute.For<IStringLocalizer<Lang.Translations>>();
public HomeControllerTests()
{
@@ -35,10 +39,10 @@ namespace WeddingShare.UnitTests.Tests.Helpers
public async Task HomeController_Index(DeviceType deviceType, bool singleGalleryMode, string secretKey, bool isRedirect)
{
_deviceDetector.ParseDeviceType(Arg.Any<string>()).Returns(deviceType);
_config.GetOrDefault("Settings", "Single_Gallery_Mode", Arg.Any<bool>()).Returns(singleGalleryMode);
_secretKey.GetGallerySecretKey(Arg.Any<string>()).Returns(secretKey);
_settings.GetOrDefault(Settings.Basic.SingleGalleryMode, Arg.Any<bool>()).Returns(singleGalleryMode);
_settings.GetOrDefault(Settings.Gallery.SecretKey, Arg.Any<string>(), Arg.Any<string>()).Returns(secretKey);
var controller = new HomeController(_config, _secretKey, _deviceDetector, _logger);
var controller = new HomeController(_settings, _database, _deviceDetector, _logger, _localizer);
controller.ControllerContext.HttpContext = new DefaultHttpContext()
{
Session = new MockSession()

View File

@@ -0,0 +1,45 @@
using WeddingShare.Extensions;
namespace WeddingShare.UnitTests.Tests.Helpers
{
public class DictionaryExtensionsTests
{
private readonly IDictionary<string, string> _data;
public DictionaryExtensionsTests()
{
_data = new Dictionary<string, string>()
{
{ "KEY_1", "1" },
{ "KEY_2", "2" },
{ "KEY_3", "3" },
{ "KEY_4", "4" },
};
}
[SetUp]
public void Setup()
{
}
[TestCase("KEY_1", "1")]
[TestCase("KEY_2", "2")]
[TestCase("KEY_3", "3")]
[TestCase("KEY_99", "")]
public void DictionaryExtensions_GetValue(string key, string expected)
{
var actual = _data.GetValue(key);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("KEY_1", "Default", "1")]
[TestCase("KEY_2", "Default", "2")]
[TestCase("KEY_3", "Default", "3")]
[TestCase("KEY_99", "Default", "Default")]
public void DictionaryExtensions_GetValue_DefaultValue(string key, string defaultValue, string? expected)
{
var actual = _data.GetValue(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
}
}

View File

@@ -58,120 +58,109 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
}
[TestCase("ENVKEY:1", "EnvValue1")]
[TestCase("ENVKEY:2", "EnvValue2")]
[TestCase("ENVKEY:3", "EnvValue3")]
[TestCase("ENVKEY:4", null)]
[TestCase("VERSION", "v2.0.0")]
[TestCase("SETTINGS:ENVKEY:1", "EnvValue1")]
[TestCase("SETTINGS:ENVKEY:2", "EnvValue2")]
[TestCase("SETTINGS:ENVKEY:3", "EnvValue3")]
[TestCase("SETTINGS:ENVKEY:4", null)]
[TestCase("RELEASE:VERSION", null)]
public void ConfigHelper_GetEnvironmentVariable(string section, string? expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetEnvironmentVariable(section);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("String1", null, "Key1", "Value1")]
[TestCase("String1", null, "Key2", "Value2")]
[TestCase("String2", null, "Key1", "Value3")]
[TestCase("String2", null, "Key2", null)]
[TestCase("Release", null, "Version", "v1.0.0")]
public void ConfigHelper_GetConfigValue(string section, string? subsection, string key, string? expected)
[TestCase("String1:Key1", "Value1")]
[TestCase("String1:Key2", "Value2")]
[TestCase("String2:Key1", "Value3")]
[TestCase("String2:Key2", null)]
[TestCase("Release:Version", "v1.0.0")]
public void ConfigHelper_GetConfigValue(string key, string? expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetConfigValue(section, subsection, key);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetConfigValue(key);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("String1", "Key1", "Value1")]
[TestCase("String1", "Key2", "Value2")]
[TestCase("String2", "Key1", "Value3")]
[TestCase("String2", "Key2", null)]
[TestCase("Release", "Version", "v1.0.0")]
public void ConfigHelper_Get(string section, string key, string? expected)
[TestCase("String1:Key1", "Value1")]
[TestCase("String1:Key2", "Value2")]
[TestCase("String2:Key1", "Value3")]
[TestCase("String2:Key2", null)]
[TestCase("Release:Version", "v1.0.0")]
public void ConfigHelper_Get(string key, string? expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).Get(section, key);
var actual = new ConfigHelper(_environment, _configuration, _logger).Get(key);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("String1", "Key1", "Default", "Value1")]
[TestCase("String1", "Key2", "Default", "Value2")]
[TestCase("String2", "Key1", "Default", "Value3")]
[TestCase("String2", "Key2", "Default", "Default")]
[TestCase("Release", "Version", "v0.0.0", "v1.0.0")]
public void ConfigHelper_GetOrDefault(string section, string key, string defaultValue, string expected)
[TestCase("String1:Key1", "Default", "Value1")]
[TestCase("String1:Key2", "Default", "Value2")]
[TestCase("String2:Key1", "Default", "Value3")]
[TestCase("String2:Key2", "Default", "Default")]
[TestCase("Release:Version", "v0.0.0", "v1.0.0")]
[TestCase("Release:Plugin:Version", "v0.0.0", "v3.0.0")]
public void ConfigHelper_GetOrDefault(string key, string defaultValue, string expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, key, defaultValue);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("String1", null, "Key1", "Default", "Value1")]
[TestCase("String1", null, "Key2", "Default", "Value2")]
[TestCase("String2", null, "Key1", "Default", "Value3")]
[TestCase("String2", null, "Key2", "Default", "Default")]
[TestCase("Release", null, "Version", "v0.0.0", "v1.0.0")]
[TestCase("Release", "Plugin", "Version", "v0.0.0", "v3.0.0")]
public void ConfigHelper_GetOrDefault(string section, string? subsection, string key, string defaultValue, string expected)
[TestCase("Int1:Key1", 999, 1)]
[TestCase("Int1:Key2", 999, 2)]
[TestCase("Int2:Key1", 999, 3)]
[TestCase("Int2:Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string key, int defaultValue, int expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, subsection, key, defaultValue);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Int1", "Key1", 999, 1)]
[TestCase("Int1", "Key2", 999, 2)]
[TestCase("Int2", "Key1", 999, 3)]
[TestCase("Int2", "Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string section, string key, int defaultValue, int expected)
[TestCase("Long1:Key1", 999, 4)]
[TestCase("Long1:Key2", 999, 5)]
[TestCase("Long2:Key1", 999, 6)]
[TestCase("Long2:Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string key, long defaultValue, long expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, key, defaultValue);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Long1", "Key1", 999, 4)]
[TestCase("Long1", "Key2", 999, 5)]
[TestCase("Long2", "Key1", 999, 6)]
[TestCase("Long2", "Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string section, string key, long defaultValue, long expected)
[TestCase("Decimal1:Key1", 999, 4.12)]
[TestCase("Decimal1:Key2", 999, 5.45)]
[TestCase("Decimal2:Key1", 999, 6.733)]
[TestCase("Decimal2:Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string key, decimal defaultValue, decimal expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, key, defaultValue);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Decimal1", "Key1", 999, 4.12)]
[TestCase("Decimal1", "Key2", 999, 5.45)]
[TestCase("Decimal2", "Key1", 999, 6.733)]
[TestCase("Decimal2", "Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string section, string key, decimal defaultValue, decimal expected)
[TestCase("Double1:Key1", 999, 4.12)]
[TestCase("Double1:Key2", 999, 5.45)]
[TestCase("Double2:Key1", 999, 6.733)]
[TestCase("Double2:Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string key, double defaultValue, double expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, key, defaultValue);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Double1", "Key1", 999, 4.12)]
[TestCase("Double1", "Key2", 999, 5.45)]
[TestCase("Double2", "Key1", 999, 6.733)]
[TestCase("Double2", "Key2", 999, 999)]
public void ConfigHelper_GetOrDefault(string section, string key, double defaultValue, double expected)
[TestCase("Boolean1:Key1", false, true)]
[TestCase("Boolean1:Key2", false, false)]
[TestCase("Boolean2:Key1", false, true)]
[TestCase("Boolean2:Key2", true, true)]
public void ConfigHelper_GetOrDefault(string key, bool defaultValue, bool expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, key, defaultValue);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Boolean1", "Key1", false, true)]
[TestCase("Boolean1", "Key2", false, false)]
[TestCase("Boolean2", "Key1", false, true)]
[TestCase("Boolean2", "Key2", true, true)]
public void ConfigHelper_GetOrDefault(string section, string key, bool defaultValue, bool expected)
[TestCase("DateTime1:Key1", null, "1987-11-20 08:00:00")]
[TestCase("DateTime1:Key2", null, "2000-08-12 12:00:00")]
[TestCase("DateTime2:Key1", null, "2018-01-01 20:30:10")]
[TestCase("DateTime2:Key2", null, null)]
[TestCase("DateTime3:Key3", "2350-05-05 00:05:12", "2350-05-05 00:05:12")]
public void ConfigHelper_GetOrDefault(string key, DateTime? defaultValue, string? expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("DateTime1", "Key1", null, "1987-11-20 08:00:00")]
[TestCase("DateTime1", "Key2", null, "2000-08-12 12:00:00")]
[TestCase("DateTime2", "Key1", null, "2018-01-01 20:30:10")]
[TestCase("DateTime2", "Key2", null, null)]
[TestCase("DateTime3", "Key3", "2350-05-05 00:05:12", "2350-05-05 00:05:12")]
public void ConfigHelper_GetOrDefault(string section, string key, DateTime? defaultValue, string? expected)
{
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(section, key, defaultValue);
var actual = new ConfigHelper(_environment, _configuration, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(!string.IsNullOrWhiteSpace(expected) ? DateTime.Parse(expected) : null));
}
}

View File

@@ -8,7 +8,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
public class EmailHelperTests
{
private readonly IConfigHelper _config = Substitute.For<IConfigHelper>();
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
private readonly ISmtpClientWrapper _smtp = Substitute.For<ISmtpClientWrapper>();
private readonly ILogger<EmailHelper> _logger = Substitute.For<ILogger<EmailHelper>>();
@@ -21,21 +21,21 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
_smtp.SendMailAsync(Arg.Any<SmtpClient>(), Arg.Any<MailMessage>()).Returns(Task.FromResult(true));
_config.GetOrDefault("Notifications", "Smtp", "Enabled", Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Notifications", "Smtp", "Recipient", Arg.Any<string>()).Returns("unit@test.com");
_config.GetOrDefault("Notifications", "Smtp", "Host", Arg.Any<string>()).Returns("https://unit.test.com/");
_config.GetOrDefault("Notifications", "Smtp", "Port", Arg.Any<int>()).Returns(999);
_config.GetOrDefault("Notifications", "Smtp", "Username", Arg.Any<string>()).Returns("Unit");
_config.GetOrDefault("Notifications", "Smtp", "Password", Arg.Any<string>()).Returns("Test");
_config.GetOrDefault("Notifications", "Smtp", "From", Arg.Any<string>()).Returns("unittest@test.com");
_config.GetOrDefault("Notifications", "Smtp", "DisplayName", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Smtp", "UseSSL", Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Smtp.Enabled, Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Smtp.Recipient, Arg.Any<string>()).Returns("unit@test.com");
_settings.GetOrDefault(Constants.Notifications.Smtp.Host, Arg.Any<string>()).Returns("https://unit.test.com/");
_settings.GetOrDefault(Constants.Notifications.Smtp.Port, Arg.Any<int>()).Returns(999);
_settings.GetOrDefault(Constants.Notifications.Smtp.Username, Arg.Any<string>()).Returns("Unit");
_settings.GetOrDefault(Constants.Notifications.Smtp.Password, Arg.Any<string>()).Returns("Test");
_settings.GetOrDefault(Constants.Notifications.Smtp.From, Arg.Any<string>()).Returns("unittest@test.com");
_settings.GetOrDefault(Constants.Notifications.Smtp.DisplayName, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Smtp.UseSSL, Arg.Any<bool>()).Returns(true);
}
[TestCase("unit", "test")]
public async Task EmailHelper_Success(string title, string message)
{
var actual = await new EmailHelper(_config, _smtp, _logger).Send(title, message);
var actual = await new EmailHelper(_settings, _smtp, _logger).Send(title, message);
Assert.That(actual, Is.EqualTo(true));
}
@@ -43,9 +43,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(false, false)]
public async Task EmailHelper_Enabled(bool enabled, bool expected)
{
_config.GetOrDefault("Notifications", "Smtp", "Enabled", Arg.Any<bool>()).Returns(enabled);
_settings.GetOrDefault(Constants.Notifications.Smtp.Enabled, Arg.Any<bool>()).Returns(enabled);
var actual = await new EmailHelper(_config, _smtp, _logger).Send("unit", "test");
var actual = await new EmailHelper(_settings, _smtp, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -54,9 +54,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("blaa@blaa.com", true)]
public async Task EmailHelper_Recipient(string recipient, bool expected)
{
_config.GetOrDefault("Notifications", "Smtp", "Recipient", Arg.Any<string>()).Returns(recipient);
_settings.GetOrDefault(Constants.Notifications.Smtp.Recipient, Arg.Any<string>()).Returns(recipient);
var actual = await new EmailHelper(_config, _smtp, _logger).Send("unit", "test");
var actual = await new EmailHelper(_settings, _smtp, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -65,9 +65,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("https://unit.test.com/", true)]
public async Task EmailHelper_Host(string host, bool expected)
{
_config.GetOrDefault("Notifications", "Smtp", "Host", Arg.Any<string>()).Returns(host);
_settings.GetOrDefault(Constants.Notifications.Smtp.Host, Arg.Any<string>()).Returns(host);
var actual = await new EmailHelper(_config, _smtp, _logger).Send("unit", "test");
var actual = await new EmailHelper(_settings, _smtp, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -77,9 +77,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(1, true)]
public async Task EmailHelper_Port(int port, bool expected)
{
_config.GetOrDefault("Notifications", "Smtp", "Port", Arg.Any<int>()).Returns(port);
_settings.GetOrDefault(Constants.Notifications.Smtp.Port, Arg.Any<int>()).Returns(port);
var actual = await new EmailHelper(_config, _smtp, _logger).Send("unit", "test");
var actual = await new EmailHelper(_settings, _smtp, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -88,9 +88,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("blaa@blaa.com", true)]
public async Task EmailHelper_From(string from, bool expected)
{
_config.GetOrDefault("Notifications", "Smtp", "From", Arg.Any<string>()).Returns(from);
_settings.GetOrDefault(Constants.Notifications.Smtp.From, Arg.Any<string>()).Returns(from);
var actual = await new EmailHelper(_config, _smtp, _logger).Send("unit", "test");
var actual = await new EmailHelper(_settings, _smtp, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -99,9 +99,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("UnitTest", true)]
public async Task EmailHelper_DisplayName(string displayName, bool expected)
{
_config.GetOrDefault("Notifications", "Smtp", "DisplayName", Arg.Any<string>()).Returns(displayName);
_settings.GetOrDefault(Constants.Notifications.Smtp.DisplayName, Arg.Any<string>()).Returns(displayName);
var actual = await new EmailHelper(_config, _smtp, _logger).Send("unit", "test");
var actual = await new EmailHelper(_settings, _smtp, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
}

View File

@@ -0,0 +1,86 @@
using WeddingShare.Constants;
using WeddingShare.Helpers;
namespace WeddingShare.UnitTests.Tests.Helpers
{
public class EncrytpionHelper
{
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
public EncrytpionHelper()
{
_settings.GetOrDefault(Security.Encryption.HashType, Arg.Any<string>()).Returns("SHA256");
_settings.GetOrDefault(Security.Encryption.Iterations, Arg.Any<int>()).Returns(1000);
}
[SetUp]
public void Setup()
{
}
[TestCase("Test", "Key1", "Salt1", "ZMw15YpZ+uph9psdR6tEZg==")]
[TestCase("Test", "Key2", "Salt2", "p/fwjLVXvJ2dRKbXDNhxDA==")]
[TestCase("Test", "Key3", "Salt3", "47VYeotX2C8GPuhaQlrWXg==")]
public void EncrytpionHelper_ValidDetails(string value, string key, string salt, string expected)
{
_settings.GetOrDefault(Security.Encryption.Key, Arg.Any<string>()).Returns(key);
_settings.GetOrDefault(Security.Encryption.Salt, Arg.Any<string>()).Returns(salt);
var actual = new EncryptionHelper(_settings).Encrypt(value);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Test1", "Salt1")]
[TestCase("Test2", "Salt2")]
[TestCase("Test3", "Salt3")]
public void EncrytpionHelper_NoKey(string value, string salt)
{
_settings.GetOrDefault(Security.Encryption.Key, Arg.Any<string>()).Returns(string.Empty);
_settings.GetOrDefault(Security.Encryption.Salt, Arg.Any<string>()).Returns(salt);
var actual = new EncryptionHelper(_settings).Encrypt(value);
Assert.That(actual, Is.EqualTo(value));
}
[TestCase("Test1", "Key1")]
[TestCase("Test2", "Key2")]
[TestCase("Test3", "Key3")]
public void EncrytpionHelper_NoSalt(string value, string key)
{
_settings.GetOrDefault(Security.Encryption.Key, Arg.Any<string>()).Returns(key);
_settings.GetOrDefault(Security.Encryption.Salt, Arg.Any<string>()).Returns(string.Empty);
var actual = new EncryptionHelper(_settings).Encrypt(value);
Assert.That(actual, Is.EqualTo(value));
}
[TestCase("Test1", "Key1", "Salt1")]
[TestCase("Test2", "Key2", "Salt2")]
[TestCase("Test3", "Key3", "Salt3")]
public void EncrytpionHelper_DifferentHashes(string value, string key, string salt)
{
_settings.GetOrDefault(Security.Encryption.Key, Arg.Any<string>()).Returns(key);
_settings.GetOrDefault(Security.Encryption.Salt, Arg.Any<string>()).Returns(salt);
var helper1 = new EncryptionHelper(_settings).Encrypt(value);
_settings.GetOrDefault(Security.Encryption.Key, Arg.Any<string>()).Returns("Unit");
_settings.GetOrDefault(Security.Encryption.Salt, Arg.Any<string>()).Returns("Test");
var helper2 = new EncryptionHelper(_settings).Encrypt(value);
Assert.That(helper1, Is.Not.EqualTo(helper2));
}
[TestCase("Key", "", false)]
[TestCase("", "Salt", false)]
[TestCase("Key", "Salt", true)]
public void EncrytpionHelper_IsEncryptionEnabled(string key, string salt, bool expected)
{
_settings.GetOrDefault(Security.Encryption.Key, Arg.Any<string>()).Returns(key);
_settings.GetOrDefault(Security.Encryption.Salt, Arg.Any<string>()).Returns(salt);
var actual = new EncryptionHelper(_settings).IsEncryptionEnabled();
Assert.That(actual, Is.EqualTo(expected));
}
}
}

View File

@@ -0,0 +1,81 @@
using Microsoft.Extensions.Logging;
using WeddingShare.Helpers;
namespace WeddingShare.UnitTests.Tests.Helpers
{
public class FileHelperTests
{
private readonly ILogger<FileHelper> _logger = Substitute.For<ILogger<FileHelper>>();
public FileHelperTests()
{
}
[SetUp]
public void Setup()
{
}
[TestCase(-1, "0 B")]
[TestCase(0, "0 B")]
[TestCase(1, "1 B")]
[TestCase(2, "2 B")]
[TestCase(3, "3 B")]
public void FileHelper_BytesToHumanReadable(long bytes, string expected)
{
var actual = new FileHelper(_logger).BytesToHumanReadable(bytes, 0);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase(-1, "0 B")]
[TestCase(0, "0 B")]
[TestCase(1, "1 B")]
[TestCase(10, "10 B")]
[TestCase(100, "100 B")]
[TestCase(1000, "1 KB")]
[TestCase(1000000, "1 MB")]
[TestCase(1000000000, "1 GB")]
[TestCase(1000000000000, "1 TB")]
[TestCase(1000000000000000, "1 PB")]
[TestCase(1000000000000000000, "1 EB")]
public void FileHelper_BytesToHumanReadable_No_Places(long bytes, string expected)
{
var actual = new FileHelper(_logger).BytesToHumanReadable(bytes, 0);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase(-1, "0.0 B")]
[TestCase(0, "0.0 B")]
[TestCase(1, "1.0 B")]
[TestCase(10, "10.0 B")]
[TestCase(100, "100.0 B")]
[TestCase(1000, "1.0 KB")]
[TestCase(1000000, "1.0 MB")]
[TestCase(1000000000, "1.0 GB")]
[TestCase(1000000000000, "1.0 TB")]
[TestCase(1000000000000000, "1.0 PB")]
[TestCase(1000000000000000000, "1.0 EB")]
public void FileHelper_BytesToHumanReadable_1_Places(long bytes, string expected)
{
var actual = new FileHelper(_logger).BytesToHumanReadable(bytes, 1);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase(-1, "0.00 B")]
[TestCase(0, "0.00 B")]
[TestCase(1, "1.00 B")]
[TestCase(10, "10.00 B")]
[TestCase(100, "100.00 B")]
[TestCase(1000, "1.00 KB")]
[TestCase(1000000, "1.00 MB")]
[TestCase(1000000000, "1.00 GB")]
[TestCase(1000000000000, "1.00 TB")]
[TestCase(1000000000000000, "1.00 PB")]
[TestCase(1000000000000000000, "1.00 EB")]
public void FileHelper_BytesToHumanReadable_2_Places(long bytes, string expected)
{
var actual = new FileHelper(_logger).BytesToHumanReadable(bytes, 2);
Assert.That(actual, Is.EqualTo(expected));
}
}
}

View File

@@ -8,7 +8,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
public class GotifyHelperTests
{
private readonly IConfigHelper _config = Substitute.For<IConfigHelper>();
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
private readonly IHttpClientFactory _clientFactory = Substitute.For<IHttpClientFactory>();
private readonly ILogger<GotifyHelper> _logger = Substitute.For<ILogger<GotifyHelper>>();
@@ -24,16 +24,16 @@ namespace WeddingShare.UnitTests.Tests.Helpers
_clientFactory.CreateClient(Arg.Any<string>()).Returns(client);
_config.GetOrDefault("Notifications", "Gotify", "Enabled", Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Notifications", "Gotify", "Endpoint", Arg.Any<string>()).Returns("https://unit.test.com/");
_config.GetOrDefault("Notifications", "Gotify", "Token", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Gotify", "Priority", Arg.Any<int>()).Returns(4);
_settings.GetOrDefault(Constants.Notifications.Gotify.Enabled, Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Gotify.Endpoint, Arg.Any<string>()).Returns("https://unit.test.com/");
_settings.GetOrDefault(Constants.Notifications.Gotify.Token, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Gotify.Priority, Arg.Any<int>()).Returns(4);
}
[TestCase("unit", "test")]
public async Task GotifyHelper_Success(string title, string message)
{
var actual = await new GotifyHelper(_config, _clientFactory, _logger).Send(title, message);
var actual = await new GotifyHelper(_settings, _clientFactory, _logger).Send(title, message);
Assert.That(actual, Is.EqualTo(true));
}
@@ -41,9 +41,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(false, false)]
public async Task GotifyHelper_Enabled(bool enabled, bool expected)
{
_config.GetOrDefault("Notifications", "Gotify", "Enabled", Arg.Any<bool>()).Returns(enabled);
_settings.GetOrDefault(Constants.Notifications.Gotify.Enabled, Arg.Any<bool>()).Returns(enabled);
var actual = await new GotifyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new GotifyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -57,7 +57,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
_clientFactory.CreateClient(Arg.Any<string>()).Returns(client);
var actual = await new GotifyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new GotifyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -66,9 +66,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("UnitTest", true)]
public async Task GotifyHelper_Token(string? token, bool expected)
{
_config.GetOrDefault("Notifications", "Gotify", "Token", Arg.Any<string>()).Returns(token);
_settings.GetOrDefault(Constants.Notifications.Gotify.Token, Arg.Any<string>()).Returns(token);
var actual = await new GotifyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new GotifyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -79,9 +79,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(100, true)]
public async Task GotifyHelper_Priority(int priority, bool expected)
{
_config.GetOrDefault("Notifications", "Gotify", "Priority", Arg.Any<int>()).Returns(priority);
_settings.GetOrDefault(Constants.Notifications.Gotify.Priority, Arg.Any<int>()).Returns(priority);
var actual = await new GotifyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new GotifyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
}

View File

@@ -1,3 +1,4 @@
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -10,6 +11,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
private readonly IFileHelper _fileHelper = Substitute.For<IFileHelper>();
private readonly ILogger<ImageHelper> _logger = Substitute.For<ILogger<ImageHelper>>();
private readonly IStringLocalizer<Lang.Translations> _localizer = Substitute.For<IStringLocalizer<Lang.Translations>>();
private readonly IDictionary<ImageOrientation, Image?> _imageCollection;
public ImageHelperTests()
@@ -35,7 +37,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
var image = _imageCollection[orientation];
Assert.IsNotNull(image);
var actual = new ImageHelper(_fileHelper, _logger).GetOrientation(image);
var actual = new ImageHelper(_fileHelper, _logger, _localizer).GetOrientation(image);
Assert.That(actual, Is.EqualTo(orientation));
}
}

View File

@@ -9,7 +9,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
public class NotificationBrokerTests
{
private readonly IConfigHelper _config = Substitute.For<IConfigHelper>();
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
private readonly IHttpClientFactory _clientFactory = Substitute.For<IHttpClientFactory>();
private readonly ISmtpClientWrapper _smtp = Substitute.For<ISmtpClientWrapper>();
private readonly ILoggerFactory _logger = Substitute.For<ILoggerFactory>();
@@ -28,26 +28,26 @@ namespace WeddingShare.UnitTests.Tests.Helpers
_smtp.SendMailAsync(Arg.Any<SmtpClient>(), Arg.Any<MailMessage>()).Returns(Task.FromResult(true));
_config.GetOrDefault("Notifications", "Smtp", "Enabled", Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Notifications", "Smtp", "Recipient", Arg.Any<string>()).Returns("unit@test.com");
_config.GetOrDefault("Notifications", "Smtp", "Host", Arg.Any<string>()).Returns("https://unit.test.com/");
_config.GetOrDefault("Notifications", "Smtp", "Port", Arg.Any<int>()).Returns(999);
_config.GetOrDefault("Notifications", "Smtp", "Username", Arg.Any<string>()).Returns("Unit");
_config.GetOrDefault("Notifications", "Smtp", "Password", Arg.Any<string>()).Returns("Test");
_config.GetOrDefault("Notifications", "Smtp", "From", Arg.Any<string>()).Returns("unittest@test.com");
_config.GetOrDefault("Notifications", "Smtp", "DisplayName", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Smtp", "UseSSL", Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Smtp.Enabled, Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Smtp.Recipient, Arg.Any<string>()).Returns("unit@test.com");
_settings.GetOrDefault(Constants.Notifications.Smtp.Host, Arg.Any<string>()).Returns("https://unit.test.com/");
_settings.GetOrDefault(Constants.Notifications.Smtp.Port, Arg.Any<int>()).Returns(999);
_settings.GetOrDefault(Constants.Notifications.Smtp.Username, Arg.Any<string>()).Returns("Unit");
_settings.GetOrDefault(Constants.Notifications.Smtp.Password, Arg.Any<string>()).Returns("Test");
_settings.GetOrDefault(Constants.Notifications.Smtp.From, Arg.Any<string>()).Returns("unittest@test.com");
_settings.GetOrDefault(Constants.Notifications.Smtp.DisplayName, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Smtp.UseSSL, Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Notifications", "Ntfy", "Enabled", Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Notifications", "Ntfy", "Endpoint", Arg.Any<string>()).Returns("https://unit.test.com/");
_config.GetOrDefault("Notifications", "Ntfy", "Token", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Ntfy", "Topic", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Ntfy", "Priority", Arg.Any<int>()).Returns(4);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Enabled, Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Endpoint, Arg.Any<string>()).Returns("https://unit.test.com/");
_settings.GetOrDefault(Constants.Notifications.Ntfy.Token, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Ntfy.Topic, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Ntfy.Priority, Arg.Any<int>()).Returns(4);
_config.GetOrDefault("Notifications", "Gotify", "Enabled", Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Notifications", "Gotify", "Endpoint", Arg.Any<string>()).Returns("https://unit.test.com/");
_config.GetOrDefault("Notifications", "Gotify", "Token", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Gotify", "Priority", Arg.Any<int>()).Returns(4);
_settings.GetOrDefault(Constants.Notifications.Gotify.Enabled, Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Gotify.Endpoint, Arg.Any<string>()).Returns("https://unit.test.com/");
_settings.GetOrDefault(Constants.Notifications.Gotify.Token, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Gotify.Priority, Arg.Any<int>()).Returns(4);
}
[TestCase(false, false, false, true)]
@@ -57,38 +57,38 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(true, true, true, true)]
public async Task NotificationBroker_Success(bool smtp, bool ntfy, bool gotify, bool expected)
{
_config.GetOrDefault("Notifications", "Smtp", "Enabled", Arg.Any<bool>()).Returns(smtp);
_config.GetOrDefault("Notifications", "Ntfy", "Enabled", Arg.Any<bool>()).Returns(ntfy);
_config.GetOrDefault("Notifications", "Gotify", "Enabled", Arg.Any<bool>()).Returns(gotify);
_settings.GetOrDefault(Constants.Notifications.Smtp.Enabled, Arg.Any<bool>()).Returns(smtp);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Enabled, Arg.Any<bool>()).Returns(ntfy);
_settings.GetOrDefault(Constants.Notifications.Gotify.Enabled, Arg.Any<bool>()).Returns(gotify);
var actual = await new NotificationBroker(_config, _smtp, _clientFactory, _logger).Send("unit", "test");
var actual = await new NotificationBroker(_settings, _smtp, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase()]
public async Task NotificationBroker_Issue_Smtp()
{
_config.GetOrDefault("Notifications", "Smtp", "Host", Arg.Any<string>()).Returns(string.Empty);
_settings.GetOrDefault(Constants.Notifications.Smtp.Host, Arg.Any<string>()).Returns(string.Empty);
var actual = await new NotificationBroker(_config, _smtp, _clientFactory, _logger).Send("unit", "test");
var actual = await new NotificationBroker(_settings, _smtp, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(false));
}
[TestCase()]
public async Task NotificationBroker_Issue_Ntfy()
{
_config.GetOrDefault("Notifications", "Ntfy", "Endpoint", Arg.Any<string>()).Returns(string.Empty);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Endpoint, Arg.Any<string>()).Returns(string.Empty);
var actual = await new NotificationBroker(_config, _smtp, _clientFactory, _logger).Send("unit", "test");
var actual = await new NotificationBroker(_settings, _smtp, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(false));
}
[TestCase()]
public async Task NotificationBroker_Issue_Gotify()
{
_config.GetOrDefault("Notifications", "Gotify", "Endpoint", Arg.Any<string>()).Returns(string.Empty);
_settings.GetOrDefault(Constants.Notifications.Gotify.Endpoint, Arg.Any<string>()).Returns(string.Empty);
var actual = await new NotificationBroker(_config, _smtp, _clientFactory, _logger).Send("unit", "test");
var actual = await new NotificationBroker(_settings, _smtp, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(false));
}
}

View File

@@ -9,7 +9,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
public class NtfyHelperTests
{
private readonly IConfigHelper _config = Substitute.For<IConfigHelper>();
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
private readonly IHttpClientFactory _clientFactory = Substitute.For<IHttpClientFactory>();
private readonly ILogger<NtfyHelper> _logger = Substitute.For<ILogger<NtfyHelper>>();
@@ -25,17 +25,17 @@ namespace WeddingShare.UnitTests.Tests.Helpers
_clientFactory.CreateClient(Arg.Any<string>()).Returns(client);
_config.GetOrDefault("Notifications", "Ntfy", "Enabled", Arg.Any<bool>()).Returns(true);
_config.GetOrDefault("Notifications", "Ntfy", "Endpoint", Arg.Any<string>()).Returns("https://unit.test.com/");
_config.GetOrDefault("Notifications", "Ntfy", "Token", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Ntfy", "Topic", Arg.Any<string>()).Returns("UnitTest");
_config.GetOrDefault("Notifications", "Ntfy", "Priority", Arg.Any<int>()).Returns(4);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Enabled, Arg.Any<bool>()).Returns(true);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Endpoint, Arg.Any<string>()).Returns("https://unit.test.com/");
_settings.GetOrDefault(Constants.Notifications.Ntfy.Token, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Ntfy.Topic, Arg.Any<string>()).Returns("UnitTest");
_settings.GetOrDefault(Constants.Notifications.Ntfy.Priority, Arg.Any<int>()).Returns(4);
}
[TestCase("unit", "test")]
public async Task NtfyHelper_Success(string title, string message)
{
var actual = await new NtfyHelper(_config, _clientFactory, _logger).Send(title, message);
var actual = await new NtfyHelper(_settings, _clientFactory, _logger).Send(title, message);
Assert.That(actual, Is.EqualTo(true));
}
@@ -43,9 +43,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(false, false)]
public async Task NtfyHelper_Enabled(bool enabled, bool expected)
{
_config.GetOrDefault("Notifications", "Ntfy", "Enabled", Arg.Any<bool>()).Returns(enabled);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Enabled, Arg.Any<bool>()).Returns(enabled);
var actual = await new NtfyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new NtfyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -59,7 +59,7 @@ namespace WeddingShare.UnitTests.Tests.Helpers
_clientFactory.CreateClient(Arg.Any<string>()).Returns(client);
var actual = await new NtfyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new NtfyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -68,9 +68,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("UnitTest", true)]
public async Task NtfyHelper_Token(string? token, bool expected)
{
_config.GetOrDefault("Notifications", "Ntfy", "Token", Arg.Any<string>()).Returns(token);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Token, Arg.Any<string>()).Returns(token);
var actual = await new NtfyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new NtfyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -79,9 +79,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("UnitTest", true)]
public async Task NtfyHelper_Topic(string? topic, bool expected)
{
_config.GetOrDefault("Notifications", "Ntfy", "Topic", Arg.Any<string>()).Returns(topic);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Topic, Arg.Any<string>()).Returns(topic);
var actual = await new NtfyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new NtfyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
@@ -92,9 +92,9 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase(100, true)]
public async Task NtfyHelper_Priority(int priority, bool expected)
{
_config.GetOrDefault("Notifications", "Ntfy", "Priority", Arg.Any<int>()).Returns(priority);
_settings.GetOrDefault(Constants.Notifications.Ntfy.Priority, Arg.Any<int>()).Returns(priority);
var actual = await new NtfyHelper(_config, _clientFactory, _logger).Send("unit", "test");
var actual = await new NtfyHelper(_settings, _clientFactory, _logger).Send("unit", "test");
Assert.That(actual, Is.EqualTo(expected));
}
}

View File

@@ -1,75 +0,0 @@
using Microsoft.Extensions.Logging;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.Models.Database;
using WeddingShare.UnitTests.Helpers;
namespace WeddingShare.UnitTests.Tests.Helpers
{
public class SecretKeyHelperTests
{
private readonly IDatabaseHelper _database = Substitute.For<IDatabaseHelper>();
public SecretKeyHelperTests()
{
_database.GetGallery("Gallery1").Returns(new GalleryModel() { SecretKey = "001" });
_database.GetGallery("Gallery2").Returns(new GalleryModel() { SecretKey = "002" });
}
[SetUp]
public void Setup()
{
}
[TestCase()]
public async Task SecretKeyHelper_GetGallerySecretKey_DefaultEnvKey()
{
var environment = Substitute.For<IEnvironmentWrapper>();
environment.GetEnvironmentVariable("SECRET_KEY").Returns("123");
var configuration = ConfigurationHelper.MockConfiguration(new Dictionary<string, string?>()
{
{ "Secret_Key_Gallery2", "002" }
});
var config = new ConfigHelper(environment, configuration, Substitute.For<ILogger<ConfigHelper>>());
var actual = await new SecretKeyHelper(config, _database).GetGallerySecretKey("Gallery3");
Assert.That(actual, Is.EqualTo("123"));
}
[TestCase()]
public async Task SecretKeyHelper_GetGallerySecretKey_GalleryEnvKey()
{
var environment = Substitute.For<IEnvironmentWrapper>();
environment.GetEnvironmentVariable("SECRET_KEY").Returns("123");
environment.GetEnvironmentVariable("SECRET_KEY_GALLERY1").Returns("001");
var configuration = ConfigurationHelper.MockConfiguration(new Dictionary<string, string?>()
{
{ "Secret_Key_Gallery2", "002" }
});
var config = new ConfigHelper(environment, configuration, Substitute.For<ILogger<ConfigHelper>>());
var actual = await new SecretKeyHelper(config, _database).GetGallerySecretKey("Gallery1");
Assert.That(actual, Is.EqualTo("001"));
}
[TestCase("Gallery1", "001")]
[TestCase("Gallery2", "002")]
[TestCase("Gallery3", null)]
public async Task SecretKeyHelper_GetGallerySecretKey_Database(string galleryId, string key)
{
var environment = Substitute.For<IEnvironmentWrapper>();
environment.GetEnvironmentVariable(Arg.Any<string>()).Returns(string.Empty);
var configuration = ConfigurationHelper.MockConfiguration(new Dictionary<string, string?>());
var config = new ConfigHelper(environment, configuration, Substitute.For<ILogger<ConfigHelper>>());
var actual = await new SecretKeyHelper(config, _database).GetGallerySecretKey(galleryId);
Assert.That(actual, Is.EqualTo(key));
}
}
}

View File

@@ -0,0 +1,175 @@
using Microsoft.Extensions.Logging;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.Models.Database;
using WeddingShare.UnitTests.Helpers;
namespace WeddingShare.UnitTests.Tests.Helpers
{
public class SettingsHelperTests
{
private readonly IConfigHelper _config;
private readonly IDatabaseHelper _database = Substitute.For<IDatabaseHelper>();
private readonly ILogger<SettingsHelper> _logger = Substitute.For<ILogger<SettingsHelper>>();
public SettingsHelperTests()
{
var environment = Substitute.For<IEnvironmentWrapper>();
environment.GetEnvironmentVariable("VERSION").Returns("v2.0.0");
environment.GetEnvironmentVariable("ENVKEY_1").Returns("EnvValue1");
environment.GetEnvironmentVariable("ENVKEY_2").Returns("EnvValue2");
environment.GetEnvironmentVariable("ENVKEY_3").Returns("EnvValue3");
var configuration = ConfigurationHelper.MockConfiguration(new Dictionary<string, string?>()
{
{ "Release:Version", "v1.0.0" },
{ "Release:Plugin:Version", "v3.0.0" },
{ "String1:Key1", "Value1" },
{ "String1:Key2", "Value2" },
{ "String2:Key1", "Value3" },
{ "Int1:Key1", "1" },
{ "Int1:Key2", "2" },
{ "Int2:Key1", "3" },
{ "Long1:Key1", "4" },
{ "Long1:Key2", "5" },
{ "Long2:Key1", "6" },
{ "Decimal1:Key1", "4.12" },
{ "Decimal1:Key2", "5.45" },
{ "Decimal2:Key1", "6.733" },
{ "Double1:Key1", "4.12" },
{ "Double1:Key2", "5.45" },
{ "Double2:Key1", "6.733" },
{ "Boolean1:Key1", "true" },
{ "Boolean1:Key2", "false" },
{ "Boolean2:Key1", "true" },
{ "DateTime1:Key1", "1987-11-20 08:00:00" },
{ "DateTime1:Key2", "2000-08-12 12:00:00" },
{ "DateTime2:Key1", "2018-01-01 20:30:10" },
});
_config = new ConfigHelper(environment, configuration, Substitute.For<ILogger<ConfigHelper>>());
_database.GetSetting("Setting1").Returns(new SettingModel() { Value = "001" });
_database.GetSetting("Setting2").Returns(new SettingModel() { Value = "002" });
_database.GetSetting("Version").Returns(new SettingModel() { Value = "v4.0.0" });
_database.AddSetting(Arg.Any<SettingModel>()).Returns(new SettingModel() { Value = "Added" });
_database.EditSetting(Arg.Any<SettingModel>()).Returns(new SettingModel() { Value = "Updated" });
_database.SetSetting(Arg.Any<SettingModel>()).Returns(new SettingModel() { Value = "Set" });
_database.DeleteSetting(Arg.Any<SettingModel>()).Returns(true);
}
[SetUp]
public void Setup()
{
}
[TestCase(null, null)]
[TestCase("", null)]
[TestCase("Setting1", "001")]
[TestCase("Setting2", "002")]
public async Task SettingsHelper_GetSetting(string key, string expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).Get(key);
Assert.That(actual?.Value, Is.EqualTo(expected));
}
[TestCase(null, null)]
[TestCase("", null)]
[TestCase("Setting1", "Set")]
public async Task SettingsHelper_SetSetting(string key, string expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).SetSetting(key, "FakeValue");
Assert.That(actual?.Value, Is.EqualTo(expected));
}
[TestCase("Setting1", true)]
public async Task SettingsHelper_DeleteSetting(string key, bool expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).DeleteSetting(key);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("String1:Key1", "Default", "Value1")]
[TestCase("String1:Key2", "Default", "Value2")]
[TestCase("String2:Key1", "Default", "Value3")]
[TestCase("String2:Key2", "Default", "Default")]
[TestCase("Release:Version", "v0.0.0", "v1.0.0")]
[TestCase("Release:Plugin:Version", "v0.0.0", "v3.0.0")]
public async Task SettingsHelper_GetOrDefault(string key, string defaultValue, string expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Int1:Key1", 999, 1)]
[TestCase("Int1:Key2", 999, 2)]
[TestCase("Int2:Key1", 999, 3)]
[TestCase("Int2:Key2", 999, 999)]
public async Task SettingsHelper_GetOrDefault(string key, int defaultValue, int expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Long1:Key1", 999, 4)]
[TestCase("Long1:Key2", 999, 5)]
[TestCase("Long2:Key1", 999, 6)]
[TestCase("Long2:Key2", 999, 999)]
public async Task SettingsHelper_GetOrDefault(string key, long defaultValue, long expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Decimal1:Key1", 999, 4.12)]
[TestCase("Decimal1:Key2", 999, 5.45)]
[TestCase("Decimal2:Key1", 999, 6.733)]
[TestCase("Decimal2:Key2", 999, 999)]
public async Task SettingsHelper_GetOrDefault(string key, decimal defaultValue, decimal expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Double1:Key1", 999, 4.12)]
[TestCase("Double1:Key2", 999, 5.45)]
[TestCase("Double2:Key1", 999, 6.733)]
[TestCase("Double2:Key2", 999, 999)]
public async Task SettingsHelper_GetOrDefault(string key, double defaultValue, double expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("Boolean1:Key1", false, true)]
[TestCase("Boolean1:Key2", false, false)]
[TestCase("Boolean2:Key1", false, true)]
[TestCase("Boolean2:Key2", true, true)]
public async Task SettingsHelper_GetOrDefault(string key, bool defaultValue, bool expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("DateTime1:Key1", null, "1987-11-20 08:00:00")]
[TestCase("DateTime1:Key2", null, "2000-08-12 12:00:00")]
[TestCase("DateTime2:Key1", null, "2018-01-01 20:30:10")]
[TestCase("DateTime2:Key2", null, null)]
[TestCase("DateTime3:Key3", "2350-05-05 00:05:12", "2350-05-05 00:05:12")]
public async Task SettingsHelper_GetOrDefault(string key, DateTime? defaultValue, string? expected)
{
var actual = await new SettingsHelper(_database, _config, _logger).GetOrDefault(key, defaultValue);
Assert.That(actual, Is.EqualTo(!string.IsNullOrWhiteSpace(expected) ? DateTime.Parse(expected) : null));
}
}
}

View File

@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
using WeddingShare.Constants;
using WeddingShare.Helpers;
using WeddingShare.UnitTests.Helpers;
@@ -6,17 +7,16 @@ namespace WeddingShare.UnitTests.Tests.Helpers
{
public class UrlHelperTests
{
private readonly IConfigHelper _config;
private readonly ISettingsHelper _settings = Substitute.For<ISettingsHelper>();
public UrlHelperTests()
{
_config = Substitute.For<IConfigHelper>();
}
[SetUp]
public void Setup()
{
_config.GetOrDefault("Settings", "Force_Https", Arg.Any<bool>()).Returns(false);
_settings.GetOrDefault(Settings.Basic.ForceHttps, Arg.Any<bool>()).Returns(false);
}
[TestCase("http", "unittest.com", null, "http://unittest.com/")]
@@ -31,15 +31,105 @@ namespace WeddingShare.UnitTests.Tests.Helpers
[TestCase("https", "unittest.org", "?unit=test", "https://unittest.org/?unit=test")]
[TestCase("http", "www.unittest.com", "?unit=test&blaa=test", "http://www.unittest.com/?unit=test&blaa=test")]
[TestCase("https", "mobile.unittest.org", "/unittest?unit=test&blaa=test", "https://mobile.unittest.org/unittest?unit=test&blaa=test")]
public async Task UrlHelper_Success(string scheme, string host, string? querystring, string expected)
public void UrlHelper_GenerateBaseUrl(string scheme, string host, string? querystring, string expected)
{
_config.GetOrDefault("Settings", "Base_Url", Arg.Any<string>()).Returns(host);
_settings.GetOrDefault(Settings.Basic.BaseUrl, Arg.Any<string>()).Returns(host);
var mockContext = MockData.MockHttpContext();
mockContext.Request.Scheme = scheme;
mockContext.Request.Host = new HostString(host);
var actual = UrlHelper.Generate(mockContext, _config, querystring);
var actual = new UrlHelper(_settings).GenerateBaseUrl(mockContext?.Request, querystring);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("?a=123&b=456&c=789", "d=111", "", "?a=123&b=456&c=789&d=111")]
[TestCase("?a=123&b=456&c=789", "d=111,e=222", "", "?a=123&b=456&c=789&d=111&e=222")]
[TestCase("?a=123&b=456&c=789", "e=222,f=333,d=111", "", "?a=123&b=456&c=789&e=222&f=333&d=111")]
[TestCase("?a=123&b=456&c=789", "d=t$&", "", "?a=123&b=456&c=789&d=t%24%26")]
public void UrlHelper_GenerateQueryString_Append(string queryString, string append, string exclude, string expected)
{
var mockContext = MockData.MockHttpContext();
mockContext.Request.Scheme = "https";
mockContext.Request.Host = new HostString("unit.test.com");
mockContext.Request.QueryString = new QueryString(queryString);
var include = append?.Split(',', StringSplitOptions.RemoveEmptyEntries)?.Select(x => {
var val = x.Split('=');
return new KeyValuePair<string, string>(val.FirstOrDefault() ?? "default", val.LastOrDefault() ?? "default");
})?.ToList();
var actual = new UrlHelper(_settings).GenerateQueryString(mockContext?.Request, include, exclude?.Split(',', StringSplitOptions.RemoveEmptyEntries)?.ToList());
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("?a=123&b=456&c=789", "", "", "?a=123&b=456&c=789")]
[TestCase("?a=123&b=456&c=789", "", "d", "?a=123&b=456&c=789")]
[TestCase("?a=123&b=456&c=789", "", "a", "?b=456&c=789")]
[TestCase("?a=123&b=456&c=789", "", "b", "?a=123&c=789")]
[TestCase("?a=123&b=456&c=789", "", "c", "?a=123&b=456")]
public void UrlHelper_GenerateQueryString_Exclude(string queryString, string append, string exclude, string expected)
{
var mockContext = MockData.MockHttpContext();
mockContext.Request.Scheme = "https";
mockContext.Request.Host = new HostString("unit.test.com");
mockContext.Request.QueryString = new QueryString(queryString);
var include = append?.Split(',', StringSplitOptions.RemoveEmptyEntries)?.Select(x => {
var val = x.Split('=');
return new KeyValuePair<string, string>(val.FirstOrDefault() ?? "default", val.LastOrDefault() ?? "default");
})?.ToList();
var actual = new UrlHelper(_settings).GenerateQueryString(mockContext?.Request, include, exclude?.Split(',', StringSplitOptions.RemoveEmptyEntries)?.ToList());
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("?a=123&b=456&c=789", "d=111", "a", "?b=456&c=789&d=111")]
[TestCase("?a=123&b=456&c=789", "d=111,e=222", "b", "?a=123&c=789&d=111&e=222")]
[TestCase("?a=123&b=456&c=789", "e=222,f=333,d=111", "c", "?a=123&b=456&e=222&f=333&d=111")]
[TestCase("?a=123&b=456&c=789", "d=111,e=222,f=333", "e", "?a=123&b=456&c=789&d=111&e=222&f=333")]
public void UrlHelper_GenerateQueryString_AppendExclude(string queryString, string append, string exclude, string expected)
{
var mockContext = MockData.MockHttpContext();
mockContext.Request.Scheme = "https";
mockContext.Request.Host = new HostString("unit.test.com");
mockContext.Request.QueryString = new QueryString(queryString);
var include = append?.Split(',', StringSplitOptions.RemoveEmptyEntries)?.Select(x => {
var val = x.Split('=');
return new KeyValuePair<string, string>(val.FirstOrDefault() ?? "default", val.LastOrDefault() ?? "default");
})?.ToList();
var actual = new UrlHelper(_settings).GenerateQueryString(mockContext?.Request, include, exclude?.Split(',', StringSplitOptions.RemoveEmptyEntries)?.ToList());
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("unit.test.com", "unit.test.com")]
[TestCase("unit.test.com/", "unit.test.com")]
[TestCase("http://unit.test.com", "unit.test.com")]
[TestCase("http://unit.test.com/", "unit.test.com")]
[TestCase("https://unit.test.com", "unit.test.com")]
[TestCase("https://unit.test.com/", "unit.test.com")]
[TestCase("http://test.com/", "test.com")]
[TestCase("https://test.com/", "test.com")]
public void UrlHelper_ExtractHost(string host, string expected)
{
var actual = new UrlHelper(_settings).ExtractHost(host);
Assert.That(actual, Is.EqualTo(expected));
}
[TestCase("?a=123&b=456&c=789", "a", "123")]
[TestCase("?a=123&b=456&c=789", "b", "456")]
[TestCase("?a=123&b=456&c=789", "c", "789")]
[TestCase("?a=123&b=456&c=789", "d", "")]
public void UrlHelper_ExtractHost(string queryString, string key, string expected)
{
var mockContext = MockData.MockHttpContext();
mockContext.Request.Scheme = "https";
mockContext.Request.Host = new HostString("unit.test.com");
mockContext.Request.QueryString = new QueryString(queryString);
var actual = new UrlHelper(_settings).ExtractQueryValue(mockContext?.Request, key);
Assert.That(actual, Is.EqualTo(expected));
}
}

View File

@@ -10,13 +10,14 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="dbup-sqlite" Version="6.0.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="NUnit" Version="3.13.3" />
<PackageReference Include="NUnit3TestAdapter" Version="4.2.1" />
<PackageReference Include="NUnit.Analyzers" Version="3.6.1" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.6" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
</ItemGroup>
<ItemGroup>

View File

@@ -1,52 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
namespace WeddingShare.Attributes
{
public class AllowGuestCreateAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
try
{
var request = filterContext.HttpContext.Request;
var galleryId = (request.Query.ContainsKey("id") && !string.IsNullOrWhiteSpace(request.Query["id"])) ? request.Query["id"].ToString().ToLower() : "default";
if (!string.Equals("default", galleryId, StringComparison.OrdinalIgnoreCase))
{
var user = filterContext?.HttpContext?.User;
if (user?.Identity == null || !user.Identity.IsAuthenticated)
{
var configHelper = filterContext.HttpContext.RequestServices.GetService<IConfigHelper>();
if (configHelper != null)
{
if (configHelper.GetOrDefault("Settings", "Disable_Guest_Gallery_Creation", true))
{
var databaseHelper = filterContext.HttpContext.RequestServices.GetService<IDatabaseHelper>();
if (databaseHelper != null)
{
var gallery = databaseHelper.GetGallery(galleryId).Result;
if (gallery == null)
{
filterContext.Result = new RedirectToActionResult("Index", "Error", new { Reason = ErrorCode.GalleryCreationNotAllowed }, false);
}
}
}
}
}
}
}
catch (Exception ex)
{
var logger = filterContext.HttpContext.RequestServices.GetService<ILogger<RequiresSecretKeyAttribute>>();
if (logger != null)
{
logger.LogError(ex, $"Failed to check guest creation - {ex?.Message}");
}
}
}
}
}

View File

@@ -1,6 +1,9 @@
using Microsoft.AspNetCore.Mvc;
using System.Web;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using WeddingShare.Constants;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
namespace WeddingShare.Attributes
{
@@ -12,22 +15,48 @@ namespace WeddingShare.Attributes
{
var request = filterContext.HttpContext.Request;
var secretKeyHelper = filterContext.HttpContext.RequestServices.GetService<ISecretKeyHelper>();
if (secretKeyHelper != null)
{
var galleryId = (request.Query.ContainsKey("id") && !string.IsNullOrWhiteSpace(request.Query["id"])) ? request.Query["id"].ToString().ToLower() : "default";
var secretKey = secretKeyHelper.GetGallerySecretKey(galleryId).Result;
var galleryId = (request.Query.ContainsKey("id") && !string.IsNullOrWhiteSpace(request.Query["id"])) ? request.Query["id"].ToString().ToLower() : "default";
var key = request.Query.ContainsKey("key") ? request.Query["key"].ToString() : string.Empty;
if (!string.IsNullOrWhiteSpace(secretKey) && !string.Equals(secretKey, key))
{
var logger = filterContext.HttpContext.RequestServices.GetService<ILogger<RequiresSecretKeyAttribute>>();
if (logger != null)
var databaseHelper = filterContext.HttpContext.RequestServices.GetService<IDatabaseHelper>();
var gallery = databaseHelper?.GetGallery(galleryId).Result;
if (gallery != null)
{
var encryptionHelper = filterContext.HttpContext.RequestServices.GetService<IEncryptionHelper>();
if (encryptionHelper != null)
{
var key = request.Query.ContainsKey("key") ? request.Query["key"].ToString() : string.Empty;
var isEncrypted = request.Query.ContainsKey("enc") ? bool.Parse(request.Query["enc"].ToString().ToLower()) : false;
if (!isEncrypted && !string.IsNullOrWhiteSpace(key) && encryptionHelper.IsEncryptionEnabled())
{
logger.LogWarning($"A request was made to an endpoint with an invalid secure key");
}
var queryString = HttpUtility.ParseQueryString(request.QueryString.ToString());
queryString.Set("enc", "true");
queryString.Set("key", encryptionHelper.Encrypt(key));
filterContext.Result = new RedirectToActionResult("Index", "Error", new { Reason = ErrorCode.InvalidSecretKey }, false);
filterContext.Result = new RedirectResult($"/Gallery?{queryString.ToString()}");
}
else
{
var settingsHelper = filterContext.HttpContext.RequestServices.GetService<ISettingsHelper>();
if (settingsHelper != null)
{
var secretKey = settingsHelper.GetOrDefault(Settings.Gallery.SecretKey, string.Empty, galleryId).Result ?? string.Empty;
if (!string.IsNullOrWhiteSpace(secretKey))
{
secretKey = encryptionHelper.IsEncryptionEnabled() ? encryptionHelper.Encrypt(secretKey) : secretKey;
if (!string.IsNullOrWhiteSpace(secretKey) && !string.Equals(secretKey, key))
{
var logger = filterContext.HttpContext.RequestServices.GetService<ILogger<RequiresSecretKeyAttribute>>();
if (logger != null)
{
logger.LogWarning($"A request was made to an endpoint with an invalid secure key");
}
filterContext.Result = new RedirectToActionResult("Index", "Error", new { Reason = ErrorCode.InvalidSecretKey }, false);
}
}
}
}
}
}
}

View File

@@ -0,0 +1,53 @@
using NCrontab;
using WeddingShare.Constants;
using WeddingShare.Helpers;
namespace WeddingShare.BackgroundWorkers
{
public sealed class CleanupService(IWebHostEnvironment hostingEnvironment, ISettingsHelper settingsHelper, IFileHelper fileHelper, ILogger<CleanupService> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var cron = await settingsHelper.GetOrDefault(BackgroundServices.Schedules.Cleanup, "0 4 * * *");
var schedule = CrontabSchedule.Parse(cron, new CrontabSchedule.ParseOptions() { IncludingSeconds = cron.Split(new[] { ' ' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Length == 6 });
await Task.Delay((int)TimeSpan.FromSeconds(10).TotalMilliseconds, stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
var now = DateTime.Now;
var nextExecutionTime = schedule.GetNextOccurrence(now);
var waitTime = nextExecutionTime - now;
await Task.Delay(waitTime, stoppingToken);
await Cleanup();
}
}
private async Task Cleanup()
{
await Task.Run(() =>
{
var paths = new List<string>()
{
Path.Combine(hostingEnvironment.WebRootPath, "temp")
};
if (paths != null)
{
foreach (var path in paths)
{
try
{
fileHelper.DeleteDirectoryIfExists(path);
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while running cleanup of '{path}'");
}
}
}
});
}
}
}

View File

@@ -1,6 +1,7 @@
using NCrontab;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using WeddingShare.Constants;
using WeddingShare.Enums;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
@@ -8,11 +9,11 @@ using WeddingShare.Models.Database;
namespace WeddingShare.BackgroundWorkers
{
public sealed class DirectoryScanner(IWebHostEnvironment hostingEnvironment, IConfigHelper configHelper, IDatabaseHelper databaseHelper, IFileHelper fileHelper, IImageHelper imageHelper, ILogger<DirectoryScanner> logger) : BackgroundService
public sealed class DirectoryScanner(IWebHostEnvironment hostingEnvironment, ISettingsHelper settingsHelper, IDatabaseHelper databaseHelper, IFileHelper fileHelper, IImageHelper imageHelper, ILogger<DirectoryScanner> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var cron = configHelper.GetOrDefault("BackgroundServices", "Directory_Scanner_Interval", "*/30 * * * *");
var cron = settingsHelper.GetOrDefault(BackgroundServices.Schedules.DirectoryScanner, "*/30 * * * *").Result;
var schedule = CrontabSchedule.Parse(cron, new CrontabSchedule.ParseOptions() { IncludingSeconds = cron.Split(new[] { ' ' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Length == 6 });
await Task.Delay((int)TimeSpan.FromSeconds(10).TotalMilliseconds, stoppingToken);
@@ -33,102 +34,118 @@ namespace WeddingShare.BackgroundWorkers
{
await Task.Run(async () =>
{
var allowedFileTypes = configHelper.GetOrDefault("Settings", "Allowed_File_Types", ".jpg,.jpeg,.png").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var thumbnailsDirectory = Path.Combine(hostingEnvironment.WebRootPath, "thumbnails");
fileHelper.CreateDirectoryIfNotExists(thumbnailsDirectory);
var uploadsDirectory = Path.Combine(hostingEnvironment.WebRootPath, "uploads");
if (fileHelper.DirectoryExists(uploadsDirectory))
if (Startup.Ready)
{
var searchPattern = !configHelper.GetOrDefault("Settings", "Single_Gallery_Mode", false) ? "*" : "default";
var galleries = fileHelper.GetDirectories(uploadsDirectory, searchPattern, SearchOption.TopDirectoryOnly)?.Where(x => !Path.GetFileName(x).StartsWith("."));
if (galleries != null)
var thumbnailsDirectory = Path.Combine(hostingEnvironment.WebRootPath, "thumbnails");
fileHelper.CreateDirectoryIfNotExists(thumbnailsDirectory);
var uploadsDirectory = Path.Combine(hostingEnvironment.WebRootPath, "uploads");
if (fileHelper.DirectoryExists(uploadsDirectory))
{
foreach (var gallery in galleries)
var searchPattern = !settingsHelper.GetOrDefault(Settings.Basic.SingleGalleryMode, false).Result ? "*" : "default";
var galleries = fileHelper.GetDirectories(uploadsDirectory, searchPattern, SearchOption.TopDirectoryOnly)?.Where(x => !Path.GetFileName(x).StartsWith("."));
if (galleries != null)
{
try
foreach (var gallery in galleries)
{
var id = Path.GetFileName(gallery).ToLower();
var galleryItem = await databaseHelper.GetGallery(id);
if (galleryItem == null)
try
{
galleryItem = await databaseHelper.AddGallery(new GalleryModel()
var id = Path.GetFileName(gallery).ToLower();
var galleryItem = await databaseHelper.GetGallery(id);
if (galleryItem == null)
{
Name = id
});
}
if (galleryItem != null)
{
var galleryItems = await databaseHelper.GetAllGalleryItems(galleryItem.Id);
if (Path.Exists(gallery))
{
var approvedFiles = fileHelper.GetFiles(gallery, "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
if (approvedFiles != null)
if (await databaseHelper.GetGalleryCount() < await settingsHelper.GetOrDefault(Settings.Basic.MaxGalleryCount, 1000000))
{
foreach (var file in approvedFiles)
galleryItem = await databaseHelper.AddGallery(new GalleryModel()
{
try
{
var filename = Path.GetFileName(file);
if (!galleryItems.Exists(x => string.Equals(x.Title, filename, StringComparison.OrdinalIgnoreCase)))
{
await databaseHelper.AddGalleryItem(new GalleryItemModel()
{
GalleryId = galleryItem.Id,
Title = filename,
State = GalleryItemState.Approved
});
}
var thumbnailPath = Path.Combine(thumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(file)}.webp");
if (!fileHelper.FileExists(thumbnailPath))
{
await imageHelper.GenerateThumbnail(file, thumbnailPath, configHelper.GetOrDefault("Settings", "Thumbnail_Size", 720));
}
else
{
using (var img = await Image.LoadAsync(thumbnailPath))
{
var width = img.Width;
img.Mutate(x => x.AutoOrient());
if (width != img.Width)
{
await img.SaveAsWebpAsync(thumbnailPath);
}
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while scanning file '{file}'");
}
}
Name = id
});
}
}
if (Path.Exists(Path.Combine(gallery, "Pending")))
if (galleryItem != null)
{
var allowedFileTypes = settingsHelper.GetOrDefault(Settings.Gallery.AllowedFileTypes, ".jpg,.jpeg,.png,.mp4,.mov", galleryItem?.Name).Result.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var galleryItems = await databaseHelper.GetAllGalleryItems(galleryItem.Id);
if (Path.Exists(gallery))
{
var pendingFiles = fileHelper.GetFiles(Path.Combine(gallery, "Pending"), "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
if (pendingFiles != null)
var approvedFiles = fileHelper.GetFiles(gallery, "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
if (approvedFiles != null)
{
foreach (var file in pendingFiles)
foreach (var file in approvedFiles)
{
try
{
var filename = Path.GetFileName(file);
if (!galleryItems.Exists(x => string.Equals(x.Title, filename, StringComparison.OrdinalIgnoreCase)))
var g = galleryItems.FirstOrDefault(x => string.Equals(x.Title, filename, StringComparison.OrdinalIgnoreCase));
if (g == null)
{
await databaseHelper.AddGalleryItem(new GalleryItemModel()
g = await databaseHelper.AddGalleryItem(new GalleryItemModel()
{
GalleryId = galleryItem.Id,
Title = filename,
State = GalleryItemState.Pending
Checksum = await fileHelper.GetChecksum(file),
MediaType = imageHelper.GetMediaType(file),
State = GalleryItemState.Approved,
UploadedDate = await fileHelper.GetCreationDatetime(file),
FileSize = fileHelper.FileSize(file),
});
}
var thumbnailPath = Path.Combine(thumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(file)}.webp");
if (!fileHelper.FileExists(thumbnailPath))
{
await imageHelper.GenerateThumbnail(file, thumbnailPath, settingsHelper.GetOrDefault(Settings.Basic.ThumbnailSize, 720).Result);
}
else
{
using (var img = await Image.LoadAsync(thumbnailPath))
{
var width = img.Width;
img.Mutate(x => x.AutoOrient());
if (width != img.Width)
{
await img.SaveAsWebpAsync(thumbnailPath);
}
}
}
if (g != null)
{
var updated = false;
if (g.UploadedDate == null)
{
g.UploadedDate = new FileInfo(file).CreationTimeUtc;
updated = true;
}
if (g.MediaType == MediaType.Unknown)
{
g.MediaType = imageHelper.GetMediaType(file);
updated = true;
}
if (g.Orientation == ImageOrientation.None)
{
g.Orientation = await imageHelper.GetOrientation(thumbnailPath);
updated = true;
}
if (g.FileSize == 0)
{
g.FileSize = fileHelper.FileSize(file);
updated = true;
}
if (updated)
{
await databaseHelper.EditGalleryItem(g);
}
}
}
catch (Exception ex)
{
@@ -136,17 +153,53 @@ namespace WeddingShare.BackgroundWorkers
}
}
}
if (Path.Exists(Path.Combine(gallery, "Pending")))
{
var pendingFiles = fileHelper.GetFiles(Path.Combine(gallery, "Pending"), "*.*", SearchOption.TopDirectoryOnly).Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
if (pendingFiles != null)
{
foreach (var file in pendingFiles)
{
try
{
var filename = Path.GetFileName(file);
if (!galleryItems.Exists(x => string.Equals(x.Title, filename, StringComparison.OrdinalIgnoreCase)))
{
await databaseHelper.AddGalleryItem(new GalleryItemModel()
{
GalleryId = galleryItem.Id,
Title = filename,
Checksum = await fileHelper.GetChecksum(file),
MediaType = imageHelper.GetMediaType(file),
State = GalleryItemState.Pending,
UploadedDate = await fileHelper.GetCreationDatetime(file),
FileSize = new FileInfo(file).Length
});
}
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while scanning file '{file}'");
}
}
}
}
}
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while scanning directory '{gallery}'");
catch (Exception ex)
{
logger.LogError(ex, $"An error occurred while scanning directory '{gallery}'");
}
}
}
}
}
else
{
logger.LogInformation($"Skipping directory scan, application not ready yet");
}
});
}
}

View File

@@ -1,18 +1,19 @@
using System.Text;
using NCrontab;
using WeddingShare.Constants;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.Helpers.Notifications;
namespace WeddingShare.BackgroundWorkers
{
public sealed class NotificationReport(IConfigHelper configHelper, IDatabaseHelper databaseHelper, ISmtpClientWrapper smtpHelper, ILoggerFactory loggerFactory) : BackgroundService
public sealed class NotificationReport(ISettingsHelper settingsHelper, IDatabaseHelper databaseHelper, ISmtpClientWrapper smtpHelper, ILoggerFactory loggerFactory) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
if (configHelper.GetOrDefault("Settings", "Email_Report", true) && configHelper.GetOrDefault("Notifications", "Smtp", "Enabled", false))
if (await settingsHelper.GetOrDefault(Settings.Basic.EmailReport, true) && await settingsHelper.GetOrDefault(Notifications.Smtp.Enabled, false))
{
var cron = configHelper.GetOrDefault("BackgroundServices", "Email_Report_Interval", "0 0 * * *");
var cron = await settingsHelper.GetOrDefault(BackgroundServices.Schedules.EmailReport, "0 0 * * *");
var schedule = CrontabSchedule.Parse(cron, new CrontabSchedule.ParseOptions() { IncludingSeconds = cron.Split(new[] { ' ' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries).Length == 6 });
while (!stoppingToken.IsCancellationRequested)
@@ -49,7 +50,7 @@ namespace WeddingShare.BackgroundWorkers
}
}
var sent = await new EmailHelper(configHelper, smtpHelper, loggerFactory.CreateLogger<EmailHelper>()).Send("Pending Items Report", builder.ToString());
var sent = await new EmailHelper(settingsHelper, smtpHelper, loggerFactory.CreateLogger<EmailHelper>()).Send("Pending Items Report", builder.ToString());
if (!sent)
{
loggerFactory.CreateLogger<NotificationReport>().LogWarning($"Failed to send notification report");

View File

@@ -1,24 +1,32 @@
using WeddingShare.Helpers;
using WeddingShare.Constants;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.Helpers.Dbup;
using WeddingShare.Models.Database;
namespace WeddingShare.Configurations
{
internal static class DatabaseConfiguration
{
public static void AddDatabaseConfiguration(this IServiceCollection services, ConfigHelper config)
public static IDatabaseHelper AddDatabaseConfiguration(this IServiceCollection services, IConfigHelper config, ILoggerFactory loggerFactory)
{
switch (config.GetOrDefault("Database", "Database_Type", "sqlite")?.ToLower())
IDatabaseHelper helper;
var databaseType = config.GetOrDefault(Settings.Database.Type, "sqlite");
switch (databaseType?.ToLower())
{
case "sqlite":
services.AddSingleton<IDatabaseHelper, SQLiteDatabaseHelper>();
case "mysql":
services.AddSingleton<IDatabaseHelper, MySqlDatabaseHelper>();
helper = new MySqlDatabaseHelper(config, loggerFactory.CreateLogger<MySqlDatabaseHelper>());
break;
default:
services.AddSingleton<IDatabaseHelper, SQLiteDatabaseHelper>();
helper = new SQLiteDatabaseHelper(config, loggerFactory.CreateLogger<SQLiteDatabaseHelper>());
break;
}
services.AddHostedService<DbupMigrator>();
return helper;
}
}
}

View File

@@ -8,11 +8,14 @@ namespace WeddingShare.Configurations
{
services.AddSingleton<IConfigHelper, ConfigHelper>();
services.AddSingleton<IEnvironmentWrapper, EnvironmentWrapper>();
services.AddSingleton<ISecretKeyHelper, SecretKeyHelper>();
services.AddSingleton<ISettingsHelper, SettingsHelper>();
services.AddSingleton<IImageHelper, ImageHelper>();
services.AddSingleton<IFileHelper, FileHelper>();
services.AddSingleton<IDeviceDetector, DeviceDetector>();
services.AddSingleton<ISmtpClientWrapper, SmtpClientWrapper>();
services.AddSingleton<IEncryptionHelper, EncryptionHelper>();
services.AddSingleton<IUrlHelper, UrlHelper>();
services.AddSingleton<ILanguageHelper, LanguageHelper>();
}
}
}

View File

@@ -0,0 +1,55 @@
using System.Globalization;
using Microsoft.AspNetCore.Localization;
using WeddingShare.Constants;
using WeddingShare.Helpers;
namespace WeddingShare.Configurations
{
public static class LocalizationConfiguration
{
public static string CurrentCulture = "en-GB";
public static void AddLocalizationConfiguration(this IServiceCollection services, SettingsHelper settings)
{
services.AddLocalization(options =>
{
options.ResourcesPath = "Resources";
});
services.Configure<RequestLocalizationOptions>(options => {
var supportedCultures = new LanguageHelper().DetectSupportedCultures();
var language = settings.GetOrDefault(Settings.Languages.Default, "en-GB").Result;
CurrentCulture = GetDefaultCulture(supportedCultures, language);
options.DefaultRequestCulture = new RequestCulture(CurrentCulture);
options.SupportedCultures = supportedCultures;
options.SupportedUICultures = supportedCultures;
});
}
private static string GetDefaultCulture(List<CultureInfo> supported, string key)
{
try
{
foreach (var culture in supported)
{
if (CultureMatches(culture, key))
{
return culture.Name;
}
}
}
catch
{
}
return "en-GB";
}
private static bool CultureMatches(CultureInfo culture, string key)
{
return string.Equals(culture.Name, key, StringComparison.OrdinalIgnoreCase) || string.Equals(culture.ThreeLetterISOLanguageName, key, StringComparison.OrdinalIgnoreCase) || string.Equals(culture.TwoLetterISOLanguageName, key, StringComparison.OrdinalIgnoreCase);
}
}
}

View File

@@ -8,21 +8,21 @@ namespace WeddingShare.Configurations
{
private const int CLIENT_DEFAULT_TIMEOUT = 10;
public static void AddNotificationConfiguration(this IServiceCollection services, ConfigHelper config)
public static void AddNotificationConfiguration(this IServiceCollection services, SettingsHelper settings)
{
services.AddSingleton<INotificationHelper, NotificationBroker>();
services.AddNtfyConfiguration(config);
services.AddGotifyConfiguration(config);
services.AddNtfyConfiguration(settings);
services.AddGotifyConfiguration(settings);
}
public static void AddNtfyConfiguration(this IServiceCollection services, ConfigHelper config)
public static void AddNtfyConfiguration(this IServiceCollection services, SettingsHelper settings)
{
services.AddHttpClient("NtfyClient", (serviceProvider, httpClient) =>
{
var endpoint = config.GetOrDefault("Notifications", "Ntfy", "Endpoint", string.Empty);
var endpoint = settings.GetOrDefault(Constants.Notifications.Ntfy.Endpoint, string.Empty).Result;
if (!string.IsNullOrWhiteSpace(endpoint))
{
var token = config.GetOrDefault("Notifications", "Ntfy", "Token", string.Empty);
var token = settings.GetOrDefault(Constants.Notifications.Ntfy.Token, string.Empty).Result;
if (!string.IsNullOrWhiteSpace(token))
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
@@ -34,11 +34,11 @@ namespace WeddingShare.Configurations
});
}
public static void AddGotifyConfiguration(this IServiceCollection services, ConfigHelper config)
public static void AddGotifyConfiguration(this IServiceCollection services, SettingsHelper settings)
{
services.AddHttpClient("GotifyClient", (serviceProvider, httpClient) =>
{
var endpoint = config.GetOrDefault("Notifications", "Gotify", "Endpoint", string.Empty);
var endpoint = settings.GetOrDefault(Constants.Notifications.Gotify.Endpoint, string.Empty).Result;
if (!string.IsNullOrWhiteSpace(endpoint))
{
httpClient.BaseAddress = new Uri(endpoint);

View File

@@ -0,0 +1,13 @@
namespace WeddingShare.Constants
{
public class BackgroundServices
{
public class Schedules
{
public const string DirectoryScanner = "BackgroundServices:Schedules:Directory_Scanner";
public const string EmailReport = "BackgroundServices:Schedules:Email_Report";
public const string Cleanup = "BackgroundServices:Schedules:Cleanup";
public const string DemoSystemReset = "BackgroundServices:Schedules:Demo_System_Reset";
}
}
}

View File

@@ -0,0 +1,7 @@
namespace WeddingShare.Constants
{
public class FFMPEG
{
public const string InstallPath = "FFMPEG:InstallPath";
}
}

View File

@@ -0,0 +1,43 @@
namespace WeddingShare.Constants
{
public class Notifications
{
public class Alerts
{
public const string FailedLogin = "Notifications:Alerts:Failed_Login";
public const string AccountLockout = "Notifications:Alerts:Account_Lockout";
public const string DestructiveAction = "Notifications:Alerts:Destructive_Action";
public const string PendingReview = "Notifications:Alerts:Pending_Review";
}
public class Gotify
{
public const string Enabled = "Notifications:Gotify:Enabled";
public const string Endpoint = "Notifications:Gotify:Endpoint";
public const string Token = "Notifications:Gotify:Token";
public const string Priority = "Notifications:Gotify:Priority";
}
public class Ntfy
{
public const string Enabled = "Notifications:Ntfy:Enabled";
public const string Endpoint = "Notifications:Ntfy:Endpoint";
public const string Token = "Notifications:Ntfy:Token";
public const string Topic = "Notifications:Ntfy:Topic";
public const string Priority = "Notifications:Ntfy:Priority";
}
public class Smtp
{
public const string Enabled = "Notifications:Smtp:Enabled";
public const string Recipient = "Notifications:Smtp:Recipient";
public const string Host = "Notifications:Smtp:Host";
public const string Port = "Notifications:Smtp:Port";
public const string Username = "Notifications:Smtp:Username";
public const string Password = "Notifications:Smtp:Password";
public const string From = "Notifications:Smtp:From";
public const string DisplayName = "Notifications:Smtp:DisplayName";
public const string UseSSL = "Notifications:Smtp:Use_SSL";
}
}
}

View File

@@ -0,0 +1,7 @@
namespace WeddingShare.Constants
{
public class Release
{
public const string Version = "Release:Version";
}
}

View File

@@ -0,0 +1,26 @@
namespace WeddingShare.Constants
{
public class Security
{
public class Encryption
{
public const string Key = "Security:Encryption:Key";
public const string Salt = "Security:Encryption:Salt";
public const string Iterations = "Security:Encryption:Iterations";
public const string HashType = "Security:Encryption:HashType";
}
public class Headers
{
public const string Enabled = "Security:Headers:Enabled";
public const string XFrameOptions = "Security:Headers:X_Frame_Options";
public const string XContentTypeOptions = "Security:Headers:X_Content_Type_Options";
public const string CSP = "Security:Headers:CSP";
}
public class MultiFactor
{
public const string ResetToDefault = "Security:2FA:Reset_To_Default";
}
}
}

View File

@@ -0,0 +1,106 @@
namespace WeddingShare.Constants
{
public class Settings
{
public const string IsDemoMode = "Settings:Demo_Mode";
public class Account
{
public const string ShowProfileIcon = "Settings:Account:Show_Profile_Icon";
public const string LockoutAttempts = "Settings:Account:Lockout_Attempts";
public const string LockoutMins = "Settings:Account:Lockout_Mins";
public class Admin
{
public const string Username = "Settings:Account:Admin:Username";
public const string Password = "Settings:Account:Admin:Password";
public const string LogPassword = "Settings:Account:Admin:Log_Password";
}
}
public class Basic
{
public const string Title = "Settings:Title";
public const string Logo = "Settings:Logo";
public const string BaseUrl = "Settings:Base_Url";
public const string ForceHttps = "Settings:Force_Https";
public const string SingleGalleryMode = "Settings:Single_Gallery_Mode";
public const string MaxGalleryCount = "Settings:Max_Gallery_Count";
public const string HomeLink = "Settings:Home_Link";
public const string GuestGalleryCreation = "Settings:Guest_Gallery_Creation";
public const string HideKeyFromQRCode = "Settings:Hide_Key_From_QR_Code";
public const string LinksOpenNewTab = "Settings:Links_Open_New_Tab";
public const string ThumbnailSize = "Settings:Thumbnail_Size";
public const string EmailReport = "Settings:Email_Report";
}
public class Database
{
public const string Type = "Settings:Database:Type";
public const string ConnectionString = "Settings:Database:Connection_String";
public const string SyncFromConfig = "Settings:Database:Sync_From_Config";
}
public class Gallery
{
public const string BannerImage = "Settings:Gallery:Banner_Image";
public const string Quote = "Settings:Gallery:Quote";
public const string SecretKey = "Settings:Gallery:Secret_Key";
public const string Columns = "Settings:Gallery:Columns";
public const string ItemsPerPage = "Settings:Gallery:Items_Per_Page";
public const string FullWidth = "Settings:Gallery:Full_Width";
public const string RetainRejectedItems = "Settings:Gallery:Retain_Rejected_Items";
public const string Upload = "Settings:Gallery:Upload";
public const string Download = "Settings:Gallery:Download";
public const string RequireReview = "Settings:Gallery:Require_Review";
public const string ReviewCounter = "Settings:Gallery:Review_Counter";
public const string PreventDuplicates = "Settings:Gallery:Prevent_Duplicates";
public const string IdleRefreshMins = "Settings:Gallery:Idle_Refresh_Mins";
public const string MaxSizeMB = "Settings:Gallery:Max_Size_MB";
public const string MaxFileSizeMB = "Settings:Gallery:Max_File_Size_MB";
public const string DefaultView = "Settings:Gallery:Default_View";
public const string UploadPeriod = "Settings:Gallery:Upload_Period";
public const string AllowedFileTypes = "Settings:Gallery:Allowed_File_Types";
public class QRCode
{
public const string Enabled = "Settings:Gallery:QR_Code:Enabled";
public const string DefaultView = "Settings:Gallery:QR_Code:Default_View";
public const string DefaultSort = "Settings:Gallery:QR_Code:Default_Sort";
}
}
public class GallerySelector
{
public const string Dropdown = "Settings:Gallery_Selector:Dropdown";
public const string HideDefaultOption = "Settings:Gallery_Selector:Hide_Default_Option";
}
public class IdentityCheck
{
public const string Enabled = "Settings:Identity_Check:Enabled";
public const string ShowOnPageLoad = "Settings:Identity_Check:Show_On_Page_Load";
public const string RequireIdentityForUpload = "Settings:Identity_Check:Require_Identity_For_Upload";
}
public class Languages
{
public const string Enabled = "Settings:Languages:Enabled";
public const string Default = "Settings:Languages:Default";
}
public class Slideshow
{
public const string Interval = "Settings:Slideshow:Interval";
public const string Fade = "Settings:Slideshow:Fade";
public const string Limit = "Settings:Slideshow:Limit";
public const string IncludeShareSlide = "Settings:Slideshow:Include_Share_Slide";
}
public class Themes
{
public const string Enabled = "Settings:Themes:Enabled";
public const string Default = "Settings:Themes:Default";
}
}
}

View File

@@ -0,0 +1,49 @@
namespace WeddingShare.Constants
{
public class ViewOptions
{
public static IDictionary<string, string> YesNo = new Dictionary<string, string>()
{
{ "Yes", "true" },
{ "No", "false" }
};
public static IDictionary<string, string> YesNoInverted = new Dictionary<string, string>()
{
{ "Yes", "false" },
{ "No", "true" }
};
public static IDictionary<string, string> SingleGalleryMode = new Dictionary<string, string>()
{
{ "Single", "true" },
{ "Multiple", "false" }
};
public static IDictionary<string, string> GallerySelectorDropdown = new Dictionary<string, string>()
{
{ "Dropdown", "true" },
{ "Input", "false" }
};
public static IDictionary<string, string> GalleryWidth = new Dictionary<string, string>()
{
{ "Full Width", "true" },
{ "Default", "false" }
};
public static IDictionary<string, string> GalleryDefaultView = new Dictionary<string, string>()
{
{ "Default", "default" },
{ "Presentation", "presentation" },
{ "Slideshow", "slideshow" }
};
public static IDictionary<string, string> GalleryDefaultSort = new Dictionary<string, string>()
{
{ "Ascending", "0" },
{ "Descending", "1" },
{ "Random", "2" }
};
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.IO.Compression;
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
@@ -5,6 +6,8 @@ using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using TwoFactorAuthNet;
using WeddingShare.Constants;
using WeddingShare.Enums;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
@@ -19,37 +22,48 @@ namespace WeddingShare.Controllers
public class AdminController : Controller
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly IConfigHelper _config;
private readonly ISettingsHelper _settings;
private readonly IDatabaseHelper _database;
private readonly IDeviceDetector _deviceDetector;
private readonly IFileHelper _fileHelper;
private readonly IImageHelper _imageHelper;
private readonly IEncryptionHelper _encryption;
private readonly INotificationHelper _notificationHelper;
private readonly Helpers.IUrlHelper _url;
private readonly ILogger _logger;
private readonly IStringLocalizer<AdminController> _localizer;
private readonly IStringLocalizer<Lang.Translations> _localizer;
private readonly string TempDirectory;
private readonly string UploadsDirectory;
private readonly string ThumbnailsDirectory;
private readonly string LogosDirectory;
private readonly string BannersDirectory;
private readonly string CustomResourcesDirectory;
public AdminController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, IDeviceDetector deviceDetector, IFileHelper fileHelper, IImageHelper imageHelper, INotificationHelper notificationHelper, ILogger<AdminController> logger, IStringLocalizer<AdminController> localizer)
public AdminController(IWebHostEnvironment hostingEnvironment, ISettingsHelper settings, IDatabaseHelper database, IDeviceDetector deviceDetector, IFileHelper fileHelper, IEncryptionHelper encryption, INotificationHelper notificationHelper, Helpers.IUrlHelper url, ILogger<AdminController> logger, IStringLocalizer<Lang.Translations> localizer)
{
_hostingEnvironment = hostingEnvironment;
_config = config;
_settings = settings;
_database = database;
_deviceDetector = deviceDetector;
_fileHelper = fileHelper;
_imageHelper = imageHelper;
_encryption = encryption;
_notificationHelper = notificationHelper;
_url = url;
_logger = logger;
_localizer = localizer;
TempDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "temp");
UploadsDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
ThumbnailsDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "thumbnails");
LogosDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "logos");
BannersDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "banners");
CustomResourcesDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "custom_resources");
}
[AllowAnonymous]
[HttpGet]
public async Task<IActionResult> Login()
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Login()
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
@@ -60,6 +74,7 @@ namespace WeddingShare.Controllers
}
[AllowAnonymous]
[ValidateAntiForgeryToken]
[HttpPost]
public async Task<IActionResult> Login(LoginModel model)
{
@@ -68,42 +83,28 @@ namespace WeddingShare.Controllers
var user = await _database.GetUser(model.Username);
if (user != null && !user.IsLockedOut)
{
if (await _database.ValidateCredentials(user.Username, model.Password))
if (await _database.ValidateCredentials(user.Username, _encryption.Encrypt(model.Password, user.Username)))
{
if (user.FailedLogins > 0)
{
await _database.ResetLockoutCount(user.Id);
}
var claims = new List<Claim>
var mfaSet = !string.IsNullOrEmpty(user.MultiFactorToken);
HttpContext.Session.SetString(SessionKey.MultiFactorTokenSet, mfaSet.ToString().ToLower());
if (mfaSet)
{
new Claim(ClaimTypes.Name, user.Username.ToLower())
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
return Json(new { success = true });
return Json(new { success = true, mfa = true });
}
else
{
return Json(new { success = await this.SetUserClaims(this.HttpContext, user), mfa = false });
}
}
else
{
if (_config.GetOrDefault("Notifications", "Alerts", "Failed_Login", true))
{
await _notificationHelper.Send("Invalid Login Detected", $"An invalid login attempt was made for account '{model?.Username}'.", UrlHelper.Generate(HttpContext, _config, "/Admin"));
}
var failedAttempts = await _database.IncrementLockoutCount(user.Id);
if (failedAttempts >= _config.GetOrDefault("Settings", "Account", "Lockout_Attempts", 5))
{
var timeout = _config.GetOrDefault("Settings", "Account", "Lockout_Mins", 60);
await _database.SetLockout(user.Id, DateTime.UtcNow.AddMinutes(timeout));
if (_config.GetOrDefault("Notifications", "Alerts", "Account_Lockout", true))
{
await _notificationHelper.Send("Account Lockout", $"Account '{model?.Username}' has been locked out for {timeout} minutes due to too many failed login attempts.", UrlHelper.Generate(HttpContext, _config, "/Admin"));
}
}
await this.FailedLoginDetected(model, user);
}
}
}
@@ -115,8 +116,59 @@ namespace WeddingShare.Controllers
return Json(new { success = false });
}
[AllowAnonymous]
[ValidateAntiForgeryToken]
[HttpPost]
public async Task<IActionResult> ValidateMultifactorAuth(LoginModel model)
{
if (!string.IsNullOrWhiteSpace(model?.Code))
{
try
{
var user = await _database.GetUser(model.Username);
if (user != null && !user.IsLockedOut)
{
if (await _database.ValidateCredentials(user.Username, _encryption.Encrypt(model.Password, user.Username)))
{
if (user.FailedLogins > 0)
{
await _database.ResetLockoutCount(user.Id);
}
var mfaSet = !string.IsNullOrWhiteSpace(user.MultiFactorToken);
HttpContext.Session.SetString(SessionKey.MultiFactorTokenSet, (!string.IsNullOrEmpty(user.MultiFactorToken)).ToString().ToLower());
if (mfaSet)
{
var tfa = new TwoFactorAuth(await _settings.GetOrDefault(Settings.Basic.Title, "WeddingShare"));
if (tfa.VerifyCode(user.MultiFactorToken, model.Code))
{
return Json(new { success = await this.SetUserClaims(this.HttpContext, user) });
}
}
else
{
return Json(new { success = await this.SetUserClaims(this.HttpContext, user) });
}
}
else
{
await this.FailedLoginDetected(model, user);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Login_Failed"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
[Authorize]
[HttpGet]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<IActionResult> Logout()
{
await this.HttpContext.SignOutAsync();
@@ -133,28 +185,44 @@ namespace WeddingShare.Controllers
var model = new IndexModel();
var deviceType = HttpContext.Session.GetString("DeviceType");
var deviceType = HttpContext.Session.GetString(SessionKey.DeviceType);
if (string.IsNullOrWhiteSpace(deviceType))
{
deviceType = (await _deviceDetector.ParseDeviceType(Request.Headers["User-Agent"].ToString())).ToString();
HttpContext.Session.SetString("DeviceType", deviceType ?? "Desktop");
HttpContext.Session.SetString(SessionKey.DeviceType, deviceType ?? "Desktop");
}
try
{
if (!_config.GetOrDefault("Settings", "Single_Gallery_Mode", false))
{
model.Galleries = await _database.GetAllGalleries();
model.PendingRequests = await _database.GetPendingGalleryItems();
}
else
{
var gallery = await _database.GetGallery("default");
if (gallery != null)
{
model.Galleries = new List<GalleryModel>() { gallery };
model.PendingRequests = await _database.GetPendingGalleryItems(gallery.Id);
var user = await _database.GetUser(int.Parse(((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(x => string.Equals(ClaimTypes.Sid, x.Type, StringComparison.OrdinalIgnoreCase))?.Value ?? "-1"));
if (user != null)
{
if (!await _settings.GetOrDefault(Settings.Basic.SingleGalleryMode, false))
{
model.Galleries = await _database.GetAllGalleries();
if (model.Galleries != null)
{
var all = await _database.GetGallery(0);
if (all != null)
{
model.Galleries.Add(all);
}
}
model.PendingRequests = await _database.GetPendingGalleryItems();
}
else
{
var gallery = await _database.GetGallery("default");
if (gallery != null)
{
model.Galleries = new List<GalleryModel>() { gallery };
model.PendingRequests = await _database.GetPendingGalleryItems(gallery.Id);
}
}
model.Users = await _database.GetAllUsers();
model.Settings = (await _database.GetAllSettings())?.ToDictionary(x => x.Id.ToUpper(), x => x.Value ?? string.Empty);
}
}
catch (Exception ex)
@@ -165,6 +233,114 @@ namespace WeddingShare.Controllers
return View(model);
}
[HttpGet]
public async Task<IActionResult> GalleriesList()
{
if (User?.Identity == null || !User.Identity.IsAuthenticated)
{
return Redirect("/");
}
List<GalleryModel>? result = null;
try
{
var user = await _database.GetUser(int.Parse(((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(x => string.Equals(ClaimTypes.Sid, x.Type, StringComparison.OrdinalIgnoreCase))?.Value ?? "-1"));
if (user != null)
{
if (!await _settings.GetOrDefault(Settings.Basic.SingleGalleryMode, false))
{
result = await _database.GetAllGalleries();
if (result != null)
{
var all = await _database.GetGallery(0);
if (all != null)
{
result.Add(all);
}
}
}
else
{
var gallery = await _database.GetGallery("default");
if (gallery != null)
{
result = new List<GalleryModel>() { gallery };
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Gallery_List_Failed"].Value} - {ex?.Message}");
}
return PartialView(result ?? new List<GalleryModel>());
}
[HttpGet]
public async Task<IActionResult> PendingReviews()
{
if (User?.Identity == null || !User.Identity.IsAuthenticated)
{
return Redirect("/");
}
List<GalleryItemModel>? result = null;
try
{
var user = await _database.GetUser(int.Parse(((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(x => string.Equals(ClaimTypes.Sid, x.Type, StringComparison.OrdinalIgnoreCase))?.Value ?? "-1"));
if (user != null)
{
if (!await _settings.GetOrDefault(Settings.Basic.SingleGalleryMode, false))
{
result = await _database.GetPendingGalleryItems();
}
else
{
var gallery = await _database.GetGallery("default");
if (gallery != null)
{
result = await _database.GetPendingGalleryItems(gallery.Id);
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Pending_Uploads_Failed"].Value} - {ex?.Message}");
}
return PartialView(result ?? new List<GalleryItemModel>());
}
[HttpGet]
public async Task<IActionResult> UsersList()
{
if (User?.Identity == null || !User.Identity.IsAuthenticated)
{
return Redirect("/");
}
List<UserModel>? result = null;
try
{
var user = await _database.GetUser(int.Parse(((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(x => string.Equals(ClaimTypes.Sid, x.Type, StringComparison.OrdinalIgnoreCase))?.Value ?? "-1"));
if (user != null)
{
result = await _database.GetAllUsers();
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Users_List_Failed"].Value} - {ex?.Message}");
}
return PartialView(result ?? new List<UserModel>());
}
[HttpPost]
public async Task<IActionResult> ReviewPhoto(int id, ReviewAction action)
{
@@ -179,10 +355,6 @@ namespace WeddingShare.Controllers
var reviewFile = Path.Combine(galleryDir, "Pending", review.Title);
if (action == ReviewAction.APPROVED)
{
_fileHelper.CreateDirectoryIfNotExists(ThumbnailsDirectory);
await _imageHelper.GenerateThumbnail(reviewFile, Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(reviewFile)}.webp"), _config.GetOrDefault("Settings", "Thumbnail_Size", 720));
_fileHelper.MoveFileIfExists(reviewFile, Path.Combine(galleryDir, review.Title));
review.State = GalleryItemState.Approved;
@@ -190,7 +362,7 @@ namespace WeddingShare.Controllers
}
else if (action == ReviewAction.REJECTED)
{
var retain = _config.GetOrDefault("Settings", "Retain_Rejected_Items", false);
var retain = await _settings.GetOrDefault(Settings.Gallery.RetainRejectedItems, false);
if (retain)
{
var rejectedDir = Path.Combine(galleryDir, "Rejected");
@@ -218,7 +390,62 @@ namespace WeddingShare.Controllers
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Reviewing_Photo"].Value} - {ex?.Message}");
_logger.LogError(ex, $"{_localizer["Failed_Reviewing_Media"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
[HttpPost]
public async Task<IActionResult> BulkReview(ReviewAction action)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
var items = await _database.GetPendingGalleryItems();
if (items != null && items.Any())
{
foreach (var review in items)
{
var galleryDir = Path.Combine(UploadsDirectory, review.GalleryName);
var reviewFile = Path.Combine(galleryDir, "Pending", review.Title);
if (action == ReviewAction.APPROVED)
{
_fileHelper.MoveFileIfExists(reviewFile, Path.Combine(galleryDir, review.Title));
review.State = GalleryItemState.Approved;
await _database.EditGalleryItem(review);
}
else if (action == ReviewAction.REJECTED)
{
var retain = await _settings.GetOrDefault(Settings.Gallery.RetainRejectedItems, false);
if (retain)
{
var rejectedDir = Path.Combine(galleryDir, "Rejected");
_fileHelper.CreateDirectoryIfNotExists(rejectedDir);
_fileHelper.MoveFileIfExists(reviewFile, Path.Combine(rejectedDir, review.Title));
}
else
{
_fileHelper.DeleteFileIfExists(reviewFile);
}
await _database.DeleteGalleryItem(review);
}
else if (action == ReviewAction.UNKNOWN)
{
throw new Exception(_localizer["Unknown_Review_Action"].Value);
}
}
}
return Json(new { success = true, action });
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Reviewing_Media"].Value} - {ex?.Message}");
}
}
@@ -237,7 +464,14 @@ namespace WeddingShare.Controllers
var check = await _database.GetGallery(model.Name);
if (check == null)
{
return Json(new { success = string.Equals(model?.Name, (await _database.AddGallery(model))?.Name, StringComparison.OrdinalIgnoreCase) });
if (await _database.GetGalleryCount() < await _settings.GetOrDefault(Settings.Basic.MaxGalleryCount, 1000000))
{
return Json(new { success = string.Equals(model?.Name, (await _database.AddGallery(model))?.Name, StringComparison.OrdinalIgnoreCase) });
}
else
{
return Json(new { success = false, message = _localizer["Gallery_Limit_Reached"].Value });
}
}
else
{
@@ -324,9 +558,9 @@ namespace WeddingShare.Controllers
_fileHelper.DeleteDirectoryIfExists(galleryDir);
_fileHelper.CreateDirectoryIfNotExists(galleryDir);
if (_config.GetOrDefault("Notifications", "Alerts", "Destructive_Action", true))
if (await _settings.GetOrDefault(Notifications.Alerts.DestructiveAction, true))
{
await _notificationHelper.Send("Destructive Action Performed", $"The destructive action 'Wipe' was performed on gallery '{gallery.Name}'.", UrlHelper.Generate(HttpContext, _config, "/Admin"));
await _notificationHelper.Send(_localizer["Destructive_Action_Performed"].Value, $"The destructive action 'Wipe' was performed on gallery '{gallery.Name}'.", _url.GenerateBaseUrl(HttpContext?.Request, "/Admin"));
}
}
@@ -367,9 +601,9 @@ namespace WeddingShare.Controllers
_fileHelper.CreateDirectoryIfNotExists(Path.Combine(UploadsDirectory, "default"));
if (_config.GetOrDefault("Notifications", "Alerts", "Destructive_Action", true))
if (await _settings.GetOrDefault(Notifications.Alerts.DestructiveAction, true))
{
await _notificationHelper.Send("Destructive Action Performed", $"The destructive action 'Wipe' was performed on all galleries'.", UrlHelper.Generate(HttpContext, _config, "/Admin"));
await _notificationHelper.Send(_localizer["Destructive_Action_Performed"].Value, $"The destructive action 'Wipe' was performed on all galleries'.", _url.GenerateBaseUrl(HttpContext?.Request, "/Admin"));
}
}
@@ -392,14 +626,14 @@ namespace WeddingShare.Controllers
try
{
var gallery = await _database.GetGallery(id);
if (gallery != null)
if (gallery != null && gallery.Id > 1)
{
var galleryDir = Path.Combine(UploadsDirectory, gallery.Name);
_fileHelper.DeleteDirectoryIfExists(galleryDir);
if (_config.GetOrDefault("Notifications", "Alerts", "Destructive_Action", true))
if (await _settings.GetOrDefault(Notifications.Alerts.DestructiveAction, true))
{
await _notificationHelper.Send("Destructive Action Performed", $"The destructive action 'Delete' was performed on gallery '{gallery.Name}'.", UrlHelper.Generate(HttpContext, _config, "/Admin"));
await _notificationHelper.Send(_localizer["Destructive_Action_Performed"].Value, $"The destructive action 'Delete' was performed on gallery '{gallery.Name}'.", _url.GenerateBaseUrl(HttpContext?.Request, "/Admin"));
}
return Json(new { success = await _database.DeleteGallery(gallery) });
@@ -452,82 +686,223 @@ namespace WeddingShare.Controllers
}
[HttpPost]
public async Task<IActionResult> DownloadGallery(int id)
public async Task<IActionResult> AddUser(UserModel model)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
if (!string.IsNullOrWhiteSpace(model?.Username) && !string.IsNullOrWhiteSpace(model?.Password) && string.Equals(model.Password, model.CPassword))
{
var gallery = await _database.GetGallery(id);
if (gallery != null)
try
{
var galleryDir = Path.Combine(UploadsDirectory, gallery.Name);
if (_fileHelper.DirectoryExists(galleryDir))
var check = await _database.GetUser(model.Username);
if (check == null)
{
var tempZipDir = $"Temp";
_fileHelper.CreateDirectoryIfNotExists(tempZipDir);
model.Password = _encryption.Encrypt(model.Password, model.Username.ToLower());
model.CPassword = string.Empty;
var tempZipFile = Path.Combine(tempZipDir, $"{gallery.Name}-{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
ZipFile.CreateFromDirectory(galleryDir, tempZipFile, CompressionLevel.Optimal, false);
byte[] bytes = await _fileHelper.ReadAllBytes(tempZipFile);
_fileHelper.DeleteFileIfExists(tempZipFile);
return Json(new { success = true, filename = Path.GetFileName(tempZipFile), content = Convert.ToBase64String(bytes, 0, bytes.Length) });
return Json(new { success = string.Equals(model?.Username, (await _database.AddUser(model))?.Username, StringComparison.OrdinalIgnoreCase) });
}
else
{
return Json(new { success = false, message = _localizer["User_Name_Already_Exists"].Value });
}
}
else
catch (Exception ex)
{
return Json(new { success = false, message = _localizer["Failed_Download_Gallery"].Value });
_logger.LogError(ex, $"{_localizer["Failed_Add_User"].Value} - {ex?.Message}");
}
}
catch (Exception ex)
else
{
_logger.LogError(ex, $"{_localizer["Failed_Download_Gallery"].Value} - {ex?.Message}");
return Json(new { success = false, message = _localizer["Failed_Add_User"].Value });
}
}
return Json(new { success = false });
}
[HttpGet]
public async Task<IActionResult> ExportBackup()
[HttpPut]
public async Task<IActionResult> EditUser(UserModel model)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var tempDir = $"Temp";
var exportDir = Path.Combine(tempDir, "Export");
if (model?.Id != null && !string.IsNullOrWhiteSpace(model?.Password) && string.Equals(model.Password, model.CPassword))
{
try
{
var user = await _database.GetUser(model.Id);
if (user != null)
{
user.Email = model.Email;
user.Password = _encryption.Encrypt(model.Password, user.Username.ToLower());
return Json(new { success = string.Equals(user?.Username, (await _database.EditUser(user))?.Username, StringComparison.OrdinalIgnoreCase) });
}
else
{
return Json(new { success = false, message = _localizer["Failed_Edit_User"].Value });
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Edit_User"].Value} - {ex?.Message}");
}
}
else
{
return Json(new { success = false, message = _localizer["Failed_Edit_User"].Value });
}
}
return Json(new { success = false });
}
[HttpDelete]
public async Task<IActionResult> DeleteUser(int id)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
var user = await _database.GetUser(id);
if (user != null && user.Id > 1)
{
if (await _settings.GetOrDefault(Notifications.Alerts.DestructiveAction, true))
{
await _notificationHelper.Send(_localizer["Destructive_Action_Performed"].Value, $"The destructive action 'Delete' was performed on user '{user.Username}'.", _url.GenerateBaseUrl(HttpContext?.Request, "/Admin"));
}
return Json(new { success = await _database.DeleteUser(user) });
}
else
{
return Json(new { success = false, message = _localizer["Failed_Delete_User"].Value });
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Delete_User"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
[HttpPut]
public async Task<IActionResult> UpdateSettings(List<UpdateSettingsModel> model)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
if (model != null && model.Count() > 0)
{
try
{
var success = true;
foreach (var m in model)
{
try
{
var setting = await _database.SetSetting(new SettingModel()
{
Id = m.Key,
Value = m.Value
});
if (setting == null || setting.Value != (m.Value ?? string.Empty))
{
success = false;
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Update_Setting"].Value} - {ex?.Message}");
}
}
return Json(new { success = success });
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Update_Setting"].Value} - {ex?.Message}");
}
}
else
{
return Json(new { success = false, message = _localizer["Failed_Update_Setting"].Value });
}
}
return Json(new { success = false });
}
[HttpPost]
public async Task<IActionResult> ExportBackup(ExportOptions options)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var exportDir = Path.Combine(TempDirectory, "Export");
try
{
if (_fileHelper.DirectoryExists(UploadsDirectory))
{
_fileHelper.CreateDirectoryIfNotExists(tempDir);
_fileHelper.CreateDirectoryIfNotExists(TempDirectory);
_fileHelper.DeleteDirectoryIfExists(exportDir);
_fileHelper.CreateDirectoryIfNotExists(exportDir);
var dbExport = Path.Combine(exportDir, $"WeddingShare.bak");
var exported = await _database.Export($"Data Source={dbExport}");
var exported = true;
if (options.Database)
{
exported = await _database.Export($"Data Source={dbExport}");
}
if (exported)
{
var uploadsZip = Path.Combine(exportDir, $"Uploads.bak");
ZipFile.CreateFromDirectory(UploadsDirectory, uploadsZip, CompressionLevel.Optimal, false);
if (options.Uploads)
{
ZipFile.CreateFromDirectory(UploadsDirectory, uploadsZip, CompressionLevel.Optimal, false);
}
var thumbnailsZip = Path.Combine(exportDir, $"Thumbnails.bak");
ZipFile.CreateFromDirectory(ThumbnailsDirectory, thumbnailsZip, CompressionLevel.Optimal, false);
if (options.Thumbnails)
{
ZipFile.CreateFromDirectory(ThumbnailsDirectory, thumbnailsZip, CompressionLevel.Optimal, false);
}
var exportZipFile = Path.Combine(tempDir, $"WeddingShare-{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
var logosZip = Path.Combine(exportDir, $"Logos.bak");
if (options.Logos && _fileHelper.DirectoryExists(LogosDirectory))
{
ZipFile.CreateFromDirectory(LogosDirectory, logosZip, CompressionLevel.Optimal, false);
}
var bannersZip = Path.Combine(exportDir, $"Banners.bak");
if (options.Banners && _fileHelper.DirectoryExists(BannersDirectory))
{
ZipFile.CreateFromDirectory(BannersDirectory, bannersZip, CompressionLevel.Optimal, false);
}
var customResourcesZip = Path.Combine(exportDir, $"CustomResources.bak");
if (options.CustomResources && _fileHelper.DirectoryExists(CustomResourcesDirectory))
{
ZipFile.CreateFromDirectory(CustomResourcesDirectory, customResourcesZip, CompressionLevel.Optimal, false);
}
var exportZipFile = Path.Combine(TempDirectory, $"WeddingShare-{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
_fileHelper.DeleteFileIfExists(exportZipFile);
ZipFile.CreateFromDirectory(exportDir, exportZipFile, CompressionLevel.Optimal, false);
_fileHelper.DeleteFileIfExists(dbExport);
_fileHelper.DeleteFileIfExists(uploadsZip);
_fileHelper.DeleteFileIfExists(thumbnailsZip);
_fileHelper.DeleteFileIfExists(logosZip);
_fileHelper.DeleteFileIfExists(bannersZip);
_fileHelper.DeleteFileIfExists(customResourcesZip);
byte[] bytes = await _fileHelper.ReadAllBytes(exportZipFile);
_fileHelper.DeleteFileIfExists(exportZipFile);
return Json(new { success = true, filename = Path.GetFileName(exportZipFile), content = Convert.ToBase64String(bytes, 0, bytes.Length) });
return Json(new { success = true, filename = $"/temp/{Path.GetFileName(exportZipFile)}" });
}
}
}
@@ -549,8 +924,7 @@ namespace WeddingShare.Controllers
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
var tempDir = $"Temp";
var importDir = Path.Combine(tempDir, "Import");
var importDir = Path.Combine(TempDirectory, "Import");
try
{
@@ -562,9 +936,9 @@ namespace WeddingShare.Controllers
var extension = Path.GetExtension(file.FileName)?.Trim('.');
if (string.Equals("zip", extension, StringComparison.OrdinalIgnoreCase))
{
_fileHelper.CreateDirectoryIfNotExists(tempDir);
_fileHelper.CreateDirectoryIfNotExists(TempDirectory);
var filePath = Path.Combine(tempDir, "Import.zip");
var filePath = Path.Combine(TempDirectory, "Import.zip");
if (!string.IsNullOrWhiteSpace(filePath))
{
await _fileHelper.SaveFile(file, filePath, FileMode.Create);
@@ -581,6 +955,24 @@ namespace WeddingShare.Controllers
var thumbnailsZip = Path.Combine(importDir, "Thumbnails.bak");
ZipFile.ExtractToDirectory(thumbnailsZip, ThumbnailsDirectory, true);
var logosZip = Path.Combine(importDir, "Logos.bak");
if (_fileHelper.FileExists(logosZip))
{
ZipFile.ExtractToDirectory(logosZip, LogosDirectory, true);
}
var bannersZip = Path.Combine(importDir, "Banners.bak");
if (_fileHelper.FileExists(bannersZip))
{
ZipFile.ExtractToDirectory(bannersZip, BannersDirectory, true);
}
var customResourcesZip = Path.Combine(importDir, "CustomResources.bak");
if (_fileHelper.FileExists(customResourcesZip))
{
ZipFile.ExtractToDirectory(customResourcesZip, CustomResourcesDirectory, true);
}
var dbImport = Path.Combine(importDir, "WeddingShare.bak");
var imported = await _database.Import($"Data Source={dbImport}");
@@ -602,5 +994,140 @@ namespace WeddingShare.Controllers
return Json(new { success = false });
}
[HttpPost]
public async Task<IActionResult> RegisterMultifactorAuth(string secret, string code)
{
if (!string.IsNullOrWhiteSpace(secret) && !string.IsNullOrWhiteSpace(code))
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
var tfa = new TwoFactorAuth(await _settings.GetOrDefault(Settings.Basic.Title, "WeddingShare"));
if (tfa.VerifyCode(secret, code))
{
var userId = int.Parse(((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(x => string.Equals(ClaimTypes.Sid, x.Type, StringComparison.OrdinalIgnoreCase))?.Value ?? "-1");
if (userId > 0)
{
var set = await _database.SetMultiFactorToken(userId, secret);
if (set)
{
HttpContext.Session.SetString(SessionKey.MultiFactorTokenSet, "true");
return Json(new { success = true });
}
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["MultiFactor_Token_Set_Failed"].Value} - {ex?.Message}");
}
}
}
return Json(new { success = false });
}
[HttpDelete]
public async Task<IActionResult> ResetMultifactorAuth()
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
var userId = int.Parse(((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(x => string.Equals(ClaimTypes.Sid, x.Type, StringComparison.OrdinalIgnoreCase))?.Value ?? "-1");
return await ResetMultifactorAuthForUser(userId);
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["MultiFactor_Token_Set_Failed"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
[HttpDelete]
public async Task<IActionResult> ResetMultifactorAuthForUser(int userId)
{
if (User?.Identity != null && User.Identity.IsAuthenticated)
{
try
{
if (userId > 0)
{
var cleared = await _database.SetMultiFactorToken(userId, string.Empty);
if (cleared)
{
var currentUserId = int.Parse(((ClaimsIdentity)User.Identity).Claims.FirstOrDefault(x => string.Equals(ClaimTypes.Sid, x.Type, StringComparison.OrdinalIgnoreCase))?.Value ?? "-1");
if (userId == currentUserId)
{
HttpContext.Session.SetString(SessionKey.MultiFactorTokenSet, "false");
}
return Json(new { success = true });
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["MultiFactor_Token_Set_Failed"].Value} - {ex?.Message}");
}
}
return Json(new { success = false });
}
private async Task<bool> SetUserClaims(HttpContext ctx, UserModel user)
{
try
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Sid, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username.ToLower())
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
await ctx.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(claimsIdentity));
return true;
}
catch
{
return false;
}
}
private async Task<bool> FailedLoginDetected(LoginModel model, UserModel user)
{
try
{
if (await _settings.GetOrDefault(Notifications.Alerts.FailedLogin, true))
{
await _notificationHelper.Send("Invalid Login Detected", $"An invalid login attempt was made for account '{model?.Username}'.", _url.GenerateBaseUrl(HttpContext?.Request, "/Admin"));
}
var failedAttempts = await _database.IncrementLockoutCount(user.Id);
if (failedAttempts >= await _settings.GetOrDefault(Settings.Account.LockoutAttempts, 5))
{
var timeout = await _settings.GetOrDefault(Settings.Account.LockoutMins, 60);
await _database.SetLockout(user.Id, DateTime.UtcNow.AddMinutes(timeout));
if (await _settings.GetOrDefault(Notifications.Alerts.AccountLockout, true))
{
await _notificationHelper.Send("Account Lockout", $"Account '{model?.Username}' has been locked out for {timeout} minutes due to too many failed login attempts.", _url.GenerateBaseUrl(HttpContext?.Request, "/Admin"));
}
}
return true;
}
catch
{
return false;
}
}
}
}

View File

@@ -1,8 +1,10 @@
using System.IO.Compression;
using System.Net;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using System.Net;
using WeddingShare.Attributes;
using WeddingShare.Constants;
using WeddingShare.Enums;
using WeddingShare.Extensions;
using WeddingShare.Helpers;
@@ -17,58 +19,105 @@ namespace WeddingShare.Controllers
public class GalleryController : Controller
{
private readonly IWebHostEnvironment _hostingEnvironment;
private readonly IConfigHelper _config;
private readonly ISettingsHelper _settings;
private readonly IDatabaseHelper _database;
private readonly IFileHelper _fileHelper;
private readonly ISecretKeyHelper _secretKey;
private readonly IDeviceDetector _deviceDetector;
private readonly IImageHelper _imageHelper;
private readonly INotificationHelper _notificationHelper;
private readonly IEncryptionHelper _encryptionHelper;
private readonly Helpers.IUrlHelper _urlHelper;
private readonly ILogger _logger;
private readonly IStringLocalizer<GalleryController> _localizer;
private readonly IStringLocalizer<Lang.Translations> _localizer;
private readonly string TempDirectory;
private readonly string UploadsDirectory;
private readonly string ThumbnailsDirectory;
public GalleryController(IWebHostEnvironment hostingEnvironment, IConfigHelper config, IDatabaseHelper database, IFileHelper fileHelper, ISecretKeyHelper secretKey, IDeviceDetector deviceDetector, IImageHelper imageHelper, INotificationHelper notificationHelper, ILogger<GalleryController> logger, IStringLocalizer<GalleryController> localizer)
public GalleryController(IWebHostEnvironment hostingEnvironment, ISettingsHelper settings, IDatabaseHelper database, IFileHelper fileHelper, IDeviceDetector deviceDetector, IImageHelper imageHelper, INotificationHelper notificationHelper, IEncryptionHelper encryptionHelper, Helpers.IUrlHelper urlHelper, ILogger<GalleryController> logger, IStringLocalizer<Lang.Translations> localizer)
{
_hostingEnvironment = hostingEnvironment;
_config = config;
_settings = settings;
_database = database;
_fileHelper = fileHelper;
_secretKey = secretKey;
_deviceDetector = deviceDetector;
_imageHelper = imageHelper;
_notificationHelper = notificationHelper;
_encryptionHelper = encryptionHelper;
_urlHelper = urlHelper;
_logger = logger;
_localizer = localizer;
TempDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "temp");
UploadsDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "uploads");
ThumbnailsDirectory = Path.Combine(_hostingEnvironment.WebRootPath, "thumbnails");
}
[HttpPost]
public async Task<IActionResult> Login(string id = "default", string? key = null)
{
var append = new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("id", id)
};
GalleryModel? gallery = await _database.GetGallery(id);
if (gallery == null)
{
if (await _settings.GetOrDefault(Settings.Basic.GuestGalleryCreation, false))
{
if (await _database.GetGalleryCount() < await _settings.GetOrDefault(Settings.Basic.MaxGalleryCount, 1000000))
{
await _database.AddGallery(new GalleryModel()
{
Name = id.ToLower(),
SecretKey = key
});
}
else
{
return new RedirectToActionResult("Index", "Error", new { Reason = ErrorCode.GalleryLimitReached }, false);
}
}
else
{
return new RedirectToActionResult("Index", "Error", new { Reason = ErrorCode.GalleryCreationNotAllowed }, false);
}
}
if (!string.IsNullOrWhiteSpace(key))
{
var enc = _encryptionHelper.IsEncryptionEnabled();
append.Add(new KeyValuePair<string, string>("key", enc ? _encryptionHelper.Encrypt(key) : key));
append.Add(new KeyValuePair<string, string>("enc", enc.ToString().ToLower()));
}
var redirectUrl = _urlHelper.GenerateFullUrl(HttpContext.Request, "/Gallery", append);
return new JsonResult(new { success = true, redirectUrl });
}
[HttpGet]
[RequiresSecretKey]
[AllowGuestCreate]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<IActionResult> Index(string id = "default", string? key = null, ViewMode? mode = null, GalleryOrder order = GalleryOrder.None)
public async Task<IActionResult> Index(string id = "default", string? key = null, ViewMode? mode = null, GalleryGroup group = GalleryGroup.None, GalleryOrder order = GalleryOrder.Descending, GalleryFilter filter = GalleryFilter.All, bool partial = false)
{
id = (!string.IsNullOrWhiteSpace(id) && !_config.GetOrDefault("Settings", "Single_Gallery_Mode", false)) ? id.ToLower() : "default";
id = (!string.IsNullOrWhiteSpace(id) && !await _settings.GetOrDefault(Settings.Basic.SingleGalleryMode, false)) ? id.ToLower() : "default";
try
{
ViewBag.ViewMode = mode ?? (ViewMode)_config.GetOrDefault("Settings", "Default_Gallery_View", (int)ViewMode.Default);
ViewBag.ViewMode = mode ?? (ViewMode)await _settings.GetOrDefault(Settings.Gallery.DefaultView, (int)ViewMode.Default, id);
}
catch
{
ViewBag.ViewMode = ViewMode.Default;
}
var deviceType = HttpContext.Session.GetString("DeviceType");
var deviceType = HttpContext.Session.GetString(SessionKey.DeviceType);
if (string.IsNullOrWhiteSpace(deviceType))
{
deviceType = (await _deviceDetector.ParseDeviceType(Request.Headers["User-Agent"].ToString())).ToString();
HttpContext.Session.SetString("DeviceType", deviceType ?? "Desktop");
HttpContext.Session.SetString(SessionKey.DeviceType, deviceType ?? "Desktop");
}
ViewBag.IsMobile = !string.Equals("Desktop", deviceType, StringComparison.OrdinalIgnoreCase);
@@ -78,56 +127,146 @@ namespace WeddingShare.Controllers
_fileHelper.CreateDirectoryIfNotExists(Path.Combine(galleryPath, "Pending"));
GalleryModel? gallery = await _database.GetGallery(id);
if (gallery == null)
{
gallery = await _database.AddGallery(new GalleryModel()
{
Name = id.ToLower(),
SecretKey = key
});
}
if (gallery != null)
{
var secretKey = await _secretKey.GetGallerySecretKey(gallery.Name);
{
ViewBag.GalleryId = gallery.Name;
var secretKey = await _settings.GetOrDefault(Settings.Gallery.SecretKey, string.Empty, gallery.Name);
ViewBag.SecretKey = secretKey;
var allowedFileTypes = _config.GetOrDefault("Settings", "Allowed_File_Types", ".jpg,.jpeg,.png").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var images = (await _database.GetAllGalleryItems(gallery.Id, GalleryItemState.Approved))?.Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x.Title).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
switch (order)
var currentPage = 1;
try
{
case GalleryOrder.UploadedAsc:
images = images?.OrderBy(x => x.Id);
currentPage = int.Parse((Request.Query.ContainsKey("page") && !string.IsNullOrWhiteSpace(Request.Query["page"])) ? Request.Query["page"].ToString().ToLower() : "1");
}
catch { }
var mediaType = MediaType.All;
if (mode == ViewMode.Slideshow)
{
mediaType = MediaType.Image;
}
else
{
switch (filter)
{
case GalleryFilter.Images:
mediaType = MediaType.Image;
break;
case GalleryFilter.Videos:
mediaType = MediaType.Video;
break;
default:
mediaType = MediaType.All;
break;
}
}
var orientation = ImageOrientation.None;
switch (filter)
{
case GalleryFilter.Landscape:
orientation = ImageOrientation.Landscape;
break;
case GalleryOrder.UploadedDesc:
images = images?.OrderByDescending(x => x.Id);
case GalleryFilter.Portrait:
orientation = ImageOrientation.Portrait;
break;
case GalleryOrder.NameAsc:
images = images?.OrderByDescending(x => x.Title);
case GalleryFilter.Square:
orientation = ImageOrientation.Square;
break;
case GalleryOrder.NameDesc:
images = images?.OrderBy(x => x.Title);
break;
default:
images = images?.OrderByDescending(x => x.Id);
default:
orientation = ImageOrientation.None;
break;
}
var itemsPerPage = await _settings.GetOrDefault(Settings.Gallery.ItemsPerPage, 50, gallery?.Name);
var allowedFileTypes = (await _settings.GetOrDefault(Settings.Gallery.AllowedFileTypes, ".jpg,.jpeg,.png,.mp4,.mov", gallery?.Name)).Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var items = (await _database.GetAllGalleryItems(gallery?.Id, GalleryItemState.Approved, mediaType, orientation, group, order, itemsPerPage, currentPage))?.Where(x => allowedFileTypes.Any(y => string.Equals(Path.GetExtension(x.Title).Trim('.'), y.Trim('.'), StringComparison.OrdinalIgnoreCase)));
var isAdmin = User?.Identity != null && User.Identity.IsAuthenticated;
FileUploader? fileUploader = null;
if (!string.Equals("All", gallery?.Name, StringComparison.OrdinalIgnoreCase) && (await _settings.GetOrDefault(Settings.Gallery.Upload, true, gallery?.Name) || isAdmin))
{
var uploadActvated = isAdmin;
try
{
if (!uploadActvated)
{
var periods = (await _settings.GetOrDefault(Settings.Gallery.UploadPeriod, "1970-01-01 00:00", gallery?.Name))?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (periods != null)
{
var now = DateTime.UtcNow;
foreach (var period in periods)
{
var timeRanges = period?.Split(new char[] { '/' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
if (timeRanges != null && timeRanges.Length > 0)
{
var startDate = DateTime.Parse(timeRanges[0]).ToUniversalTime();
if (timeRanges.Length == 2)
{
var endDate = DateTime.Parse(timeRanges[1]).ToUniversalTime();
if (now >= startDate && now < endDate)
{
uploadActvated = true;
break;
}
}
else if (timeRanges.Length == 1 && now >= startDate)
{
uploadActvated = true;
break;
}
}
}
}
}
}
catch
{
uploadActvated = true;
}
if (uploadActvated)
{
fileUploader = new FileUploader(gallery?.Name ?? "default", secretKey, "/Gallery/UploadImage", await _settings.GetOrDefault(Settings.IdentityCheck.RequireIdentityForUpload, false));
}
}
var itemCounts = await _database.GetGalleryItemCount(gallery?.Id, GalleryItemState.All, mediaType, orientation);
var model = new PhotoGallery()
{
GalleryId = id,
GalleryPath = $"/{galleryPath.Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}",
ThumbnailsPath = $"/{ThumbnailsDirectory.Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}",
Images = images?.Select(x => new PhotoGalleryImage() { Id = x.Id, Name = Path.GetFileName(x.Title), Path = x.Title, UploadedBy = x.UploadedBy })?.ToList(),
PendingCount = gallery?.PendingItems ?? 0,
FileUploader = !_config.GetOrDefault("Settings", "Disable_Upload", false) || (User?.Identity != null && User.Identity.IsAuthenticated) ? new FileUploader(id, secretKey, "/Gallery/UploadImage") : null,
ViewMode = (ViewMode)ViewBag.ViewMode
GalleryId = gallery?.Id,
GalleryName = gallery?.Name,
Images = items?.Select(x => new PhotoGalleryImage()
{
Id = x.Id,
GalleryId = x.GalleryId,
GalleryName = x.GalleryName,
Name = Path.GetFileName(x.Title),
UploadedBy = x.UploadedBy,
UploadDate = x.UploadedDate,
ImagePath = $"/{Path.Combine(UploadsDirectory, x.GalleryName).Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}/{x.Title}",
ThumbnailPath = $"/{ThumbnailsDirectory.Remove(_hostingEnvironment.WebRootPath).Replace('\\', '/').TrimStart('/')}/{Path.GetFileNameWithoutExtension(x.Title)}.webp",
MediaType = x.MediaType
})?.ToList(),
CurrentPage = currentPage,
ApprovedCount = (int)itemCounts["Approved"],
PendingCount = (int)itemCounts["Pending"],
ItemsPerPage = itemsPerPage,
FileUploader = fileUploader,
ViewMode = (ViewMode)ViewBag.ViewMode,
GroupBy = group,
OrderBy = order,
Pagination = order != GalleryOrder.Random,
LoadScripts = !partial
};
return View(model);
return partial ? PartialView("~/Views/Gallery/GalleryWrapper.cshtml", model) : View(model);
}
return View(new PhotoGallery());
return new RedirectToActionResult("Index", "Error", new { Reason = ErrorCode.InvalidGalleryId }, false);
}
[HttpPost]
@@ -146,19 +285,19 @@ namespace WeddingShare.Controllers
var gallery = await _database.GetGallery(galleryId);
if (gallery != null)
{
var secretKey = await _secretKey.GetGallerySecretKey(galleryId);
var secretKey = await _settings.GetOrDefault(Settings.Gallery.SecretKey, string.Empty, galleryId);
string key = (Request?.Form?.FirstOrDefault(x => string.Equals("SecretKey", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString() ?? string.Empty;
if (!string.IsNullOrWhiteSpace(secretKey) && !string.Equals(secretKey, key))
{
return Json(new { success = false, uploaded = 0, errors = new List<string>() { _localizer["Invalid_Secret_Key_Warning"].Value } });
}
string uploadedBy = (Request?.Form?.FirstOrDefault(x => string.Equals("UploadedBy", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString() ?? string.Empty;
string uploadedBy = HttpContext.Session.GetString(SessionKey.ViewerIdentity) ?? "Anonymous";
var files = Request?.Form?.Files;
if (files != null && files.Count > 0)
{
var requiresReview = _config.GetOrDefault("Settings", "Require_Review", true);
var requiresReview = await _settings.GetOrDefault(Settings.Gallery.RequireReview, true, galleryId);
var uploaded = 0;
var errors = new List<string>();
@@ -167,46 +306,65 @@ namespace WeddingShare.Controllers
try
{
var extension = Path.GetExtension(file.FileName);
var maxFilesSize = _config.GetOrDefault("Settings", "Max_File_Size_Mb", 10) * 1000000;
var maxGallerySize = await _settings.GetOrDefault(Settings.Gallery.MaxSizeMB, 1024L, galleryId) * 1000000;
var maxFilesSize = await _settings.GetOrDefault(Settings.Gallery.MaxFileSizeMB, 10L, galleryId) * 1000000;
var galleryPath = Path.Combine(UploadsDirectory, gallery.Name);
var allowedFileTypes = _config.GetOrDefault("Settings", "Allowed_File_Types", ".jpg,.jpeg,.png").Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
var allowedFileTypes = (await _settings.GetOrDefault(Settings.Gallery.AllowedFileTypes, ".jpg,.jpeg,.png,.mp4,.mov", galleryId)).Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
if (!allowedFileTypes.Any(x => string.Equals(x.Trim('.'), extension.Trim('.'), StringComparison.OrdinalIgnoreCase)))
{
errors.Add($"{_localizer["File_Upload_Failed"].Value} '{Path.GetFileName(file.FileName)}'. {_localizer["Invalid_File_Type"].Value}");
errors.Add($"{_localizer["File_Upload_Failed"].Value}. {_localizer["Invalid_File_Type"].Value}");
}
else if (file.Length > maxFilesSize)
{
errors.Add($"{_localizer["File_Upload_Failed"].Value} '{Path.GetFileName(file.FileName)}'. {_localizer["Max_File_Size"].Value} {maxFilesSize} bytes");
errors.Add($"{_localizer["File_Upload_Failed"].Value}. {_localizer["Max_File_Size"].Value} {maxFilesSize} bytes");
}
else if ((_fileHelper.GetDirectorySize(galleryPath) + file.Length) > maxGallerySize)
{
errors.Add($"{_localizer["File_Upload_Failed"].Value}. {_localizer["Gallery_Full"].Value} {maxGallerySize} bytes");
}
else
{
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
var galleryPath = requiresReview ? Path.Combine(UploadsDirectory, gallery.Name, "Pending") : Path.Combine(UploadsDirectory, gallery.Name);
galleryPath = requiresReview ? Path.Combine(galleryPath, "Pending") : galleryPath;
_fileHelper.CreateDirectoryIfNotExists(galleryPath);
var filePath = Path.Combine(galleryPath, fileName);
if (!string.IsNullOrWhiteSpace(filePath))
{
await _fileHelper.SaveFile(file, filePath, FileMode.Create);
await _fileHelper.SaveFile(file, filePath, FileMode.Create);
if (!requiresReview)
{
_fileHelper.CreateDirectoryIfNotExists(ThumbnailsDirectory);
await _imageHelper.GenerateThumbnail(filePath, Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(filePath)}.webp"), _config.GetOrDefault("Settings", "Thumbnail_Size", 720));
}
var item = await _database.AddGalleryItem(new GalleryItemModel()
var checksum = await _fileHelper.GetChecksum(filePath);
if (await _settings.GetOrDefault(Settings.Gallery.PreventDuplicates, true, galleryId) && (string.IsNullOrWhiteSpace(checksum) || await _database.GetGalleryItemByChecksum(gallery.Id, checksum) != null))
{
GalleryId = gallery.Id,
Title = fileName,
UploadedBy = uploadedBy,
State = requiresReview ? GalleryItemState.Pending : GalleryItemState.Approved
});
errors.Add($"{_localizer["File_Upload_Failed"].Value}. {_localizer["Duplicate_Item_Detected"].Value}");
_fileHelper.DeleteFileIfExists(filePath);
}
else
{
var savePath = Path.Combine(ThumbnailsDirectory, $"{Path.GetFileNameWithoutExtension(filePath)}.webp");
if (item?.Id > 0)
{
uploaded++;
_fileHelper.CreateDirectoryIfNotExists(ThumbnailsDirectory);
await _imageHelper.GenerateThumbnail(filePath, savePath, await _settings.GetOrDefault(Settings.Basic.ThumbnailSize, 720));
var item = await _database.AddGalleryItem(new GalleryItemModel()
{
GalleryId = gallery.Id,
Title = fileName,
UploadedBy = uploadedBy,
UploadedDate = await _fileHelper.GetCreationDatetime(filePath),
Checksum = checksum,
MediaType = _imageHelper.GetMediaType(filePath),
Orientation = await _imageHelper.GetOrientation(savePath),
State = requiresReview ? GalleryItemState.Pending : GalleryItemState.Approved,
FileSize = file.Length,
});
if (item?.Id > 0)
{
uploaded++;
}
}
}
}
@@ -219,13 +377,7 @@ namespace WeddingShare.Controllers
Response.StatusCode = (int)HttpStatusCode.OK;
var filesUploaded = uploaded > 0;
if (filesUploaded && requiresReview && _config.GetOrDefault("Notifications", "Alerts", "Pending_Review", true))
{
await _notificationHelper.Send("New Items Pending Review", $"{uploaded} new item(s) have been uploaded to gallery '{gallery.Name}' by '{(!string.IsNullOrWhiteSpace(uploadedBy) ? uploadedBy : "Anonymous")}' and are awaiting your review.", UrlHelper.Generate(HttpContext, _config, "/Admin"));
}
return Json(new { success = filesUploaded, uploaded, uploadedBy, requiresReview, errors });
return Json(new { success = uploaded > 0, uploaded, uploadedBy, requiresReview, errors });
}
else
{
@@ -244,5 +396,105 @@ namespace WeddingShare.Controllers
return Json(new { success = false, uploaded = 0 });
}
[HttpPost]
public async Task<IActionResult> UploadCompleted()
{
Response.StatusCode = (int)HttpStatusCode.BadRequest;
try
{
string galleryId = (Request?.Form?.FirstOrDefault(x => string.Equals("Id", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString()?.ToLower() ?? string.Empty;
if (string.IsNullOrWhiteSpace(galleryId))
{
return Json(new { success = false, uploaded = 0, errors = new List<string>() { _localizer["Invalid_Gallery_Id"].Value } });
}
var gallery = await _database.GetGallery(galleryId);
if (gallery != null)
{
var secretKey = await _settings.GetOrDefault(Settings.Gallery.SecretKey, string.Empty, galleryId);
string key = (Request?.Form?.FirstOrDefault(x => string.Equals("SecretKey", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString() ?? string.Empty;
if (!string.IsNullOrWhiteSpace(secretKey) && !string.Equals(secretKey, key))
{
return Json(new { success = false, uploaded = 0, errors = new List<string>() { _localizer["Invalid_Secret_Key_Warning"].Value } });
}
var uploadedBy = HttpContext.Session.GetString(SessionKey.ViewerIdentity) ?? "Anonymous";
var requiresReview = await _settings.GetOrDefault(Settings.Gallery.RequireReview, true, galleryId);
int uploaded = int.Parse((Request?.Form?.FirstOrDefault(x => string.Equals("Count", x.Key, StringComparison.OrdinalIgnoreCase)).Value)?.ToString() ?? "0");
if (uploaded > 0 && requiresReview && await _settings.GetOrDefault(Notifications.Alerts.PendingReview, true))
{
await _notificationHelper.Send(_localizer["New_Items_Pending_Review"].Value, $"{uploaded} new item(s) have been uploaded to gallery '{gallery.Name}' by '{(!string.IsNullOrWhiteSpace(uploadedBy) ? uploadedBy : "Anonymous")}' and are awaiting your review.", _urlHelper.GenerateBaseUrl(HttpContext?.Request, "/Admin"));
}
Response.StatusCode = (int)HttpStatusCode.OK;
return Json(new { success = true, counters = new { total = gallery?.TotalItems ?? 0, approved = gallery?.ApprovedItems ?? 0, pending = gallery?.PendingItems ?? 0 }, uploaded, uploadedBy, requiresReview });
}
else
{
return Json(new { success = false, uploaded = 0, errors = new List<string>() { _localizer["Gallery_Does_Not_Exist"].Value } });
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Image_Upload_Failed"].Value} - {ex?.Message}");
}
return Json(new { success = false });
}
[HttpPost]
public async Task<IActionResult> DownloadGallery(int id)
{
try
{
var gallery = await _database.GetGallery(id);
if (gallery != null)
{
if (await _settings.GetOrDefault(Settings.Gallery.Download, true, gallery?.Name) || (User?.Identity != null && User.Identity.IsAuthenticated))
{
var galleryDir = id > 0 ? Path.Combine(UploadsDirectory, gallery.Name) : UploadsDirectory;
if (_fileHelper.DirectoryExists(galleryDir))
{
_fileHelper.CreateDirectoryIfNotExists(TempDirectory);
var tempZipFile = Path.Combine(TempDirectory, $"{gallery.Name}-{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss")}.zip");
ZipFile.CreateFromDirectory(galleryDir, tempZipFile, CompressionLevel.Optimal, false);
if (User?.Identity == null || !User.Identity.IsAuthenticated)
{
using (var fs = new FileStream(tempZipFile, FileMode.Open, FileAccess.ReadWrite))
using (var archive = new ZipArchive(fs, ZipArchiveMode.Update, false))
{
foreach (var entry in archive.Entries.Where(x => x.FullName.StartsWith("Pending/", StringComparison.OrdinalIgnoreCase) || x.FullName.StartsWith("Rejected/", StringComparison.OrdinalIgnoreCase)).ToList())
{
entry.Delete();
}
}
}
return Json(new { success = true, filename = $"/temp/{Path.GetFileName(tempZipFile)}" });
}
}
else
{
return Json(new { success = false, message = _localizer["Download_Gallery_Not_Allowed"].Value });
}
}
else
{
return Json(new { success = false, message = _localizer["Failed_Download_Gallery"].Value });
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Failed_Download_Gallery"].Value} - {ex?.Message}");
}
return Json(new { success = false });
}
}
}

View File

@@ -1,52 +1,88 @@
using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using WeddingShare.Constants;
using WeddingShare.Helpers;
using WeddingShare.Helpers.Database;
using WeddingShare.Models;
namespace WeddingShare.Controllers
{
[AllowAnonymous]
public class HomeController : Controller
{
private readonly IConfigHelper _config;
private readonly ISecretKeyHelper _secretKey;
private readonly ISettingsHelper _settings;
private readonly IDatabaseHelper _database;
private readonly IDeviceDetector _deviceDetector;
private readonly ILogger _logger;
private readonly IStringLocalizer<Lang.Translations> _localizer;
public HomeController(IConfigHelper config, ISecretKeyHelper secretKey, IDeviceDetector deviceDetector, ILogger<HomeController> logger)
public HomeController(ISettingsHelper settings, IDatabaseHelper database, IDeviceDetector deviceDetector, ILogger<HomeController> logger, IStringLocalizer<Lang.Translations> localizer)
{
_config = config;
_secretKey = secretKey;
_settings = settings;
_database = database;
_deviceDetector = deviceDetector;
_logger = logger;
_localizer = localizer;
}
[HttpGet]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public async Task<IActionResult> Index()
{
var model = new Views.Home.IndexModel();
try
{
var deviceType = HttpContext.Session.GetString("DeviceType");
var deviceType = HttpContext.Session.GetString(SessionKey.DeviceType);
if (string.IsNullOrWhiteSpace(deviceType))
{
deviceType = (await _deviceDetector.ParseDeviceType(Request.Headers["User-Agent"].ToString())).ToString();
HttpContext.Session.SetString("DeviceType", deviceType ?? "Desktop");
HttpContext.Session.SetString(SessionKey.DeviceType, deviceType ?? "Desktop");
}
if (_config.GetOrDefault("Settings", "Single_Gallery_Mode", false))
if (await _settings.GetOrDefault(Settings.Basic.SingleGalleryMode, false))
{
var key = await _secretKey.GetGallerySecretKey("default");
var key = await _settings.GetOrDefault(Settings.Gallery.SecretKey, string.Empty, "default");
if (string.IsNullOrWhiteSpace(key))
{
return RedirectToAction("Index", "Gallery");
}
}
model.GalleryNames = await _settings.GetOrDefault(Settings.GallerySelector.Dropdown, false) ? await _database.GetGalleryNames() : new List<string>() { "default" };
if (await _settings.GetOrDefault(Settings.GallerySelector.HideDefaultOption, false))
{
model.GalleryNames = model.GalleryNames.Where(x => !x.Equals("default", StringComparison.OrdinalIgnoreCase));
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"An error occurred loading the homepage - {ex?.Message}");
_logger.LogError(ex, $"{_localizer["Homepage_Load_Error"].Value} - {ex?.Message}");
}
return View();
return View(model);
}
[HttpPost]
public IActionResult SetIdentity(string name)
{
try
{
if (Regex.IsMatch(name, @"^[a-zA-Z-\s\-\']+$", RegexOptions.Compiled))
{
HttpContext.Session.SetString(SessionKey.ViewerIdentity, name);
return Json(new { success = true });
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"{_localizer["Identity_Session_Error"].Value}: '{name}'");
}
return Json(new { success = false });
}
}
}

View File

@@ -0,0 +1,65 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Localization;
using Microsoft.AspNetCore.Mvc;
using WeddingShare.Constants;
using WeddingShare.Helpers;
using WeddingShare.Models;
namespace WeddingShare.Controllers
{
[AllowAnonymous]
public class LanguageController : Controller
{
private readonly ISettingsHelper _settings;
private readonly ILanguageHelper _languageHelper;
public LanguageController(ISettingsHelper settings, ILanguageHelper languageHelper)
{
_settings = settings;
_languageHelper = languageHelper;
}
[HttpGet]
public async Task<IActionResult> Index()
{
var options = new List<SupportedLanguage>();
try
{
var defaultLang = HttpContext.Session.GetString(SessionKey.SelectedLanguage);
if (string.IsNullOrWhiteSpace(defaultLang))
{
defaultLang = await _settings.GetOrDefault(Settings.Languages.Default, "en-GB");
}
options = (await _languageHelper.DetectSupportedCulturesAsync())
.Where(x => x.Name.Contains("-"))
.Select(x => new SupportedLanguage() { Key = x.Name, Value = $"{(x.EnglishName.Contains("(") ? x.EnglishName.Substring(0, x.EnglishName.IndexOf("(")) : x.EnglishName).Trim()} ({x.Name})", Selected = string.Equals(defaultLang, x.Name, StringComparison.OrdinalIgnoreCase) })
.OrderBy(x => x.Value.ToLower())
.ToList();
}
catch { }
return Json(new { supported = options });
}
[HttpPost]
public async Task<IActionResult> ChangeDisplayLanguage(string culture)
{
try
{
HttpContext.Session.SetString(SessionKey.SelectedLanguage, culture);
Response.Cookies.Append(
CookieRequestCultureProvider.DefaultCookieName,
CookieRequestCultureProvider.MakeCookieValue(new RequestCulture(culture)),
new CookieOptions { Expires = DateTimeOffset.UtcNow.AddYears(1) }
);
return Json(new { success = true });
}
catch { }
return Json(new { success = false });
}
}
}

View File

@@ -4,18 +4,19 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 5000
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:8.0 AS build
ARG TARGETARCH
ARG BUILD_CONFIGURATION=Release
WORKDIR /src
COPY ["WeddingShare/WeddingShare.csproj", "WeddingShare/"]
RUN dotnet restore "./WeddingShare/./WeddingShare.csproj"
RUN dotnet restore -a $TARGETARCH "./WeddingShare/./WeddingShare.csproj"
COPY . .
WORKDIR "/src/WeddingShare"
RUN dotnet build "./WeddingShare.csproj" -c $BUILD_CONFIGURATION -o /app/build
RUN dotnet build "./WeddingShare.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/build
FROM build AS publish
ARG BUILD_CONFIGURATION=Release
RUN dotnet publish "./WeddingShare.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
RUN dotnet publish "./WeddingShare.csproj" -a $TARGETARCH -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false
FROM base AS final
WORKDIR /app

View File

@@ -3,6 +3,7 @@
public enum DatabaseType
{
Unknown,
SQLite
SQLite,
MySQL
}
}

View File

@@ -0,0 +1,12 @@
namespace WeddingShare.Enums
{
public enum GalleryFilter
{
All,
Images,
Videos,
Landscape,
Portrait,
Square
}
}

View File

@@ -0,0 +1,10 @@
namespace WeddingShare.Enums
{
public enum GalleryGroup
{
None,
Date,
MediaType,
Uploader
}
}

View File

@@ -2,10 +2,8 @@
{
public enum GalleryOrder
{
None,
UploadedAsc,
UploadedDesc,
NameAsc,
NameDesc
Ascending,
Descending,
Random
}
}

View File

@@ -0,0 +1,10 @@
namespace WeddingShare.Enums
{
public enum MediaType
{
Unknown,
Image,
Video,
All
}
}

View File

@@ -0,0 +1,8 @@
namespace WeddingShare.Enums
{
public enum Themes
{
Default = 1,
Dark = 2
}
}

View File

@@ -0,0 +1,19 @@
namespace WeddingShare.Extensions
{
public static class DictionaryExtensions
{
public static string GetValue(this IDictionary<string, string> value, string key, string defaultValue = "")
{
try
{
if (value.ContainsKey(key))
{
return value[key];
}
}
catch { }
return defaultValue;
}
}
}

View File

@@ -2,24 +2,16 @@
{
public interface IConfigHelper
{
string? GetEnvironmentVariable(string key);
string? GetConfigValue(string section, string? subsection, string key);
string? Get(string section, string key);
string? Get(string section, string? subsection, string key);
string GetOrDefault(string section, string key, string defaultValue);
string GetOrDefault(string section, string? subsection, string key, string defaultValue);
int GetOrDefault(string section, string key, int defaultValue);
int GetOrDefault(string section, string? subsection, string key, int defaultValue);
long GetOrDefault(string section, string key, long defaultValue);
long GetOrDefault(string section, string? subsection, string key, long defaultValue);
decimal GetOrDefault(string section, string key, decimal defaultValue);
decimal GetOrDefault(string section, string? subsection, string key, decimal defaultValue);
double GetOrDefault(string section, string key, double defaultValue);
double GetOrDefault(string section, string? subsection, string key, double defaultValue);
bool GetOrDefault(string section, string key, bool defaultValue);
bool GetOrDefault(string section, string? subsection, string key, bool defaultValue);
DateTime? GetOrDefault(string section, string key, DateTime? defaultValue);
DateTime? GetOrDefault(string section, string? subsection, string key, DateTime? defaultValue);
string? GetEnvironmentVariable(string key, string? galleryId = null);
string? GetConfigValue(string key);
string? Get(string key, string? galleryId = null);
string GetOrDefault(string key, string defaultValue);
int GetOrDefault(string key, int defaultValue);
long GetOrDefault(string key, long defaultValue);
decimal GetOrDefault(string key, decimal defaultValue);
double GetOrDefault(string key, double defaultValue);
bool GetOrDefault(string key, bool defaultValue);
DateTime? GetOrDefault(string key, DateTime? defaultValue);
}
public class ConfigHelper : IConfigHelper
@@ -35,32 +27,35 @@
_logger = logger;
}
public string? GetEnvironmentVariable(string key)
public string? GetEnvironmentVariable(string key, string? galleryId = null)
{
try
{
var value = _environment.GetEnvironmentVariable(key.Replace(":", "_").Trim('_').ToUpper());
if (!string.IsNullOrWhiteSpace(value))
var envKey = !string.IsNullOrWhiteSpace(galleryId) ? $"{key}_{galleryId}" : key;
if (!this.IsProtectedVariable(envKey))
{
return value;
var keyName = string.Join('_', envKey.Split(':', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).Skip(1)).Trim('_').ToUpper();
var value = _environment.GetEnvironmentVariable(keyName);
if (!string.IsNullOrWhiteSpace(value))
{
return value;
}
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to get environment variable '{key}'");
_logger.LogWarning(ex, $"Failed to get environment variable '{key}' for gallery '{galleryId}'");
}
return null;
}
public string? GetConfigValue(string section, string? subsection, string key)
public string? GetConfigValue(string key)
{
try
{
section = !string.IsNullOrWhiteSpace(subsection) ? $"{section}:{subsection}" : section;
var configKey = !string.IsNullOrWhiteSpace(section) ? $"{section}:{key}" : key;
var value = _configuration.GetValue<string>(configKey);
var value = _configuration.GetValue<string>(key);
if (!string.IsNullOrWhiteSpace(value))
{
return value;
@@ -74,25 +69,26 @@
return null;
}
public string? Get(string section, string key)
{
return this.Get(section, null, key);
}
public string? Get(string section, string? subsection, string key)
public string? Get(string key, string? galleryId = null)
{
try
{
var value = !IsProtectedVariable($"{CombineSections(section, subsection)}_{key}") ? this.GetEnvironmentVariable($"{subsection}_{key}") : string.Empty;
if (!string.IsNullOrWhiteSpace(value))
var envValue = this.GetEnvironmentVariable(key, galleryId);
if (!string.IsNullOrWhiteSpace(envValue))
{
return value;
return envValue;
}
value = this.GetConfigValue(section, subsection, key);
if (!string.IsNullOrWhiteSpace(value))
envValue = this.GetEnvironmentVariable(key);
if (!string.IsNullOrWhiteSpace(envValue))
{
return value;
return envValue;
}
var configValue = this.GetConfigValue(key);
if (!string.IsNullOrWhiteSpace(configValue))
{
return configValue;
}
}
catch (Exception ex)
@@ -103,16 +99,11 @@
return null;
}
public string GetOrDefault(string section, string key, string defaultValue)
{
return this.GetOrDefault(section, null, key, defaultValue);
}
public string GetOrDefault(string section, string? subsection, string key, string defaultValue)
public string GetOrDefault(string key, string defaultValue)
{
try
{
var value = this.Get(section, subsection, key);
var value = this.Get(key);
if (!string.IsNullOrWhiteSpace(value))
{
return value;
@@ -123,16 +114,11 @@
return defaultValue;
}
public int GetOrDefault(string section, string key, int defaultValue)
{
return this.GetOrDefault(section, null, key, defaultValue);
}
public int GetOrDefault(string section, string? subsection, string key, int defaultValue)
public int GetOrDefault(string key, int defaultValue)
{
try
{
var value = this.GetOrDefault(section, subsection, key, string.Empty);
var value = this.GetOrDefault(key, string.Empty);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToInt32(value);
@@ -143,16 +129,11 @@
return defaultValue;
}
public long GetOrDefault(string section, string key, long defaultValue)
{
return this.GetOrDefault(section, null, key, defaultValue);
}
public long GetOrDefault(string section, string? subsection, string key, long defaultValue)
public long GetOrDefault(string key, long defaultValue)
{
try
{
var value = this.GetOrDefault(section, subsection, key, string.Empty);
var value = this.GetOrDefault(key, string.Empty);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToInt64(value);
@@ -163,16 +144,11 @@
return defaultValue;
}
public decimal GetOrDefault(string section, string key, decimal defaultValue)
{
return this.GetOrDefault(section, null, key, defaultValue);
}
public decimal GetOrDefault(string section, string? subsection, string key, decimal defaultValue)
public decimal GetOrDefault(string key, decimal defaultValue)
{
try
{
var value = this.GetOrDefault(section, subsection, key, string.Empty);
var value = this.GetOrDefault(key, string.Empty);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToDecimal(value);
@@ -183,16 +159,11 @@
return defaultValue;
}
public double GetOrDefault(string section, string key, double defaultValue)
{
return this.GetOrDefault(section, null, key, defaultValue);
}
public double GetOrDefault(string section, string? subsection, string key, double defaultValue)
public double GetOrDefault(string key, double defaultValue)
{
try
{
var value = this.GetOrDefault(section, subsection, key, string.Empty);
var value = this.GetOrDefault(key, string.Empty);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToDouble(value);
@@ -203,16 +174,11 @@
return defaultValue;
}
public bool GetOrDefault(string section, string key, bool defaultValue)
{
return this.GetOrDefault(section, null, key, defaultValue);
}
public bool GetOrDefault(string section, string? subsection, string key, bool defaultValue)
public bool GetOrDefault(string key, bool defaultValue)
{
try
{
var value = this.GetOrDefault(section, subsection, key, string.Empty);
var value = this.GetOrDefault(key, string.Empty);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToBoolean(value);
@@ -223,16 +189,11 @@
return defaultValue;
}
public DateTime? GetOrDefault(string section, string key, DateTime? defaultValue)
{
return this.GetOrDefault(section, null, key, defaultValue);
}
public DateTime? GetOrDefault(string section, string? subsection, string key, DateTime? defaultValue)
public DateTime? GetOrDefault(string key, DateTime? defaultValue)
{
try
{
var value = this.GetOrDefault(section, subsection, key, string.Empty);
var value = this.GetOrDefault(key, string.Empty);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToDateTime(value);
@@ -243,11 +204,6 @@
return defaultValue;
}
private string CombineSections(string section, string? subsection)
{
return $"{section}_{subsection}".Trim('_');
}
private bool IsProtectedVariable(string key)
{
switch (key.Replace(":", "_").Trim('_').ToUpper())

View File

@@ -5,7 +5,11 @@ namespace WeddingShare.Helpers.Database
{
public interface IDatabaseHelper
{
Task<int> GetGalleryCount();
Task<IEnumerable<string>> GetGalleryNames();
Task<List<GalleryModel>> GetAllGalleries();
Task<int?> GetGalleryId(string name);
Task<string?> GetGalleryName(int id);
Task<GalleryModel?> GetGallery(int id);
Task<GalleryModel?> GetGallery(string name);
Task<GalleryModel?> AddGallery(GalleryModel model);
@@ -13,16 +17,19 @@ namespace WeddingShare.Helpers.Database
Task<bool> WipeGallery(GalleryModel model);
Task<bool> WipeAllGalleries();
Task<bool> DeleteGallery(GalleryModel model);
Task<List<GalleryItemModel>> GetAllGalleryItems(int galleryId, GalleryItemState state = GalleryItemState.All);
Task<IDictionary<string, long>> GetGalleryItemCount(int? galleryId, GalleryItemState state = GalleryItemState.All, MediaType type = MediaType.All, ImageOrientation orientation = ImageOrientation.None);
Task<List<GalleryItemModel>> GetAllGalleryItems(int? galleryId, GalleryItemState state = GalleryItemState.All, MediaType type = MediaType.All, ImageOrientation orientation = ImageOrientation.None, GalleryGroup group = GalleryGroup.None, GalleryOrder order = GalleryOrder.Descending, int limit = int.MaxValue, int page = 1);
Task<int> GetPendingGalleryItemCount(int? galleryId = null);
Task<List<PendingGalleryItemModel>> GetPendingGalleryItems(int? galleryId = null);
Task<PendingGalleryItemModel?> GetPendingGalleryItem(int id);
Task<List<GalleryItemModel>> GetPendingGalleryItems(int? galleryId = null);
Task<GalleryItemModel?> GetPendingGalleryItem(int id);
Task<GalleryItemModel?> GetGalleryItem(int id);
Task<GalleryItemModel?> GetGalleryItemByChecksum(int galleryId, string checksum);
Task<GalleryItemModel?> AddGalleryItem(GalleryItemModel model);
Task<GalleryItemModel?> EditGalleryItem(GalleryItemModel model);
Task<bool> DeleteGalleryItem(GalleryItemModel model);
Task<bool> InitAdminAccount(UserModel model);
Task<bool> ValidateCredentials(string username, string password);
Task<List<UserModel>?> GetAllUsers();
Task<UserModel?> GetUser(int id);
Task<UserModel?> GetUser(string name);
Task<UserModel?> AddUser(UserModel model);
@@ -32,7 +39,17 @@ namespace WeddingShare.Helpers.Database
Task<int> IncrementLockoutCount(int id);
Task<bool> SetLockout(int id, DateTime? datetime);
Task<bool> ResetLockoutCount(int id);
Task<bool> SetMultiFactorToken(int id, string token);
Task<bool> ResetMultiFactorToDefault();
Task<bool> Import(string path);
Task<bool> Export(string path);
Task<IEnumerable<SettingModel>?> GetAllSettings(string? gallery = "");
Task<SettingModel?> GetSetting(string id, string? gallery = "");
Task<SettingModel?> GetGallerySpecificSetting(string id, string gallery);
Task<SettingModel?> AddSetting(SettingModel model, string? gallery = "");
Task<SettingModel?> EditSetting(SettingModel model, string? gallery = "");
Task<SettingModel?> SetSetting(SettingModel model, string? gallery = "");
Task<bool> DeleteSetting(SettingModel model, string? gallery = "");
Task<bool> DeleteAllSettings(string? gallery = "");
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,55 +1,80 @@
using System.Reflection;
using DbUp;
using DbUp.Engine;
using WeddingShare.Constants;
using WeddingShare.Enums;
using WeddingShare.Helpers.Database;
using WeddingShare.Models.Database;
namespace WeddingShare.Helpers.Dbup
{
public sealed class DbupMigrator(IEnvironmentWrapper environment, IConfiguration configuration, IDatabaseHelper database, IFileHelper fileHelper, ILoggerFactory loggerFactory) : BackgroundService
public sealed class DbupMigrator(IEnvironmentWrapper environment, IConfiguration configuration, IDatabaseHelper database, IFileHelper fileHelper, IEncryptionHelper encryption, ILoggerFactory loggerFactory) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await Task.Run(() =>
var logger = loggerFactory.CreateLogger<DbupMigrator>();
fileHelper.CreateDirectoryIfNotExists("config");
var config = new ConfigHelper(environment, configuration, loggerFactory.CreateLogger<ConfigHelper>());
var connString = config.GetOrDefault(Settings.Database.ConnectionString, "Data Source=./config/wedding-share.db");
if (!string.IsNullOrWhiteSpace(connString))
{
var logger = loggerFactory.CreateLogger<DbupMigrator>();
DatabaseUpgradeResult? dbupResult;
fileHelper.CreateDirectoryIfNotExists("config");
var config = new ConfigHelper(environment, configuration, loggerFactory.CreateLogger<ConfigHelper>());
var connString = config.GetOrDefault("Database", "Connection_String", "Data Source=./config/wedding-share.db");
if (!string.IsNullOrWhiteSpace(connString))
var dbType = config.GetOrDefault(Settings.Database.Type, "sqlite")?.ToLower();
switch (dbType)
{
DatabaseUpgradeResult? dbupResult;
case "sqlite":
dbupResult = new DbupSqliteHelper().Migrate(connString);
break;
case "mysql":
dbupResult = new DbupMySqlHelper().Migrate(connString);
break;
default:
var error = $"Database type '{dbType}' is not yet supported by this application";
logger.LogWarning(error);
throw new NotImplementedException(error);
}
var dbType = config.GetOrDefault("Database", "Database_Type", "sqlite")?.ToLower();
switch (dbType)
{
case "sqlite":
dbupResult = new DbupSqliteHelper().Migrate(connString);
break;
default:
var error = $"Database type '{dbType}' is not yet supported by this application";
logger.LogWarning(error);
throw new NotImplementedException(error);
}
if (dbupResult != null && !dbupResult.Successful)
{
logger.LogWarning($"DBUP failed with error: '{dbupResult?.Error?.Message}' - '{dbupResult?.Error?.ToString()}'");
}
if (dbupResult != null && !dbupResult.Successful)
{
logger.LogWarning($"DBUP failed with error: '{dbupResult?.Error?.Message}' - '{dbupResult?.Error?.ToString()}'");
}
if (config.GetOrDefault(Settings.Database.SyncFromConfig, false))
{
logger.LogWarning($"Sync_From_Config set to true, wiping settings database and re-pulling values from config");
await database.DeleteAllSettings();
}
var adminAccount = new UserModel() { Username = config.GetOrDefault("Settings", "Admin", "Username", "admin"), Password = config.GetOrDefault("Settings", "Admin", "Password", "admin") };
database.InitAdminAccount(adminAccount);
var isDemoMode = config.GetOrDefault(Settings.IsDemoMode, false);
var username = !isDemoMode ? config.GetOrDefault(Settings.Account.Admin.Username, "admin").ToLower() : "demo";
var adminAccount = new UserModel()
{
Username = username,
Password = encryption.Encrypt(!isDemoMode ? config.GetOrDefault(Settings.Account.Admin.Password, "admin") : "demo", username)
};
await database.InitAdminAccount(adminAccount);
await new DbupImporter(config, database, loggerFactory.CreateLogger<DbupImporter>()).ImportSettings();
if (config.GetOrDefault(Settings.Account.Admin.LogPassword, false))
{
logger.LogInformation($"Password: {adminAccount.Password}");
}
else
if (config.GetOrDefault(Security.MultiFactor.ResetToDefault, false))
{
logger.LogError($"DBUP failed with error: 'Connection string was null or empty'");
throw new ArgumentNullException("Please specify a valid database connection string");
await database.ResetMultiFactorToDefault();
}
}, stoppingToken);
}
else
{
logger.LogError($"DBUP failed with error: 'Connection string was null or empty'");
throw new ArgumentNullException("Please specify a valid database connection string");
}
}
}
@@ -60,12 +85,35 @@ namespace WeddingShare.Helpers.Dbup
try
{
var dbupBuilder = DeployChanges.To
.SQLiteDatabase(connectionString)
.SqliteDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.WithScriptNameComparer(new DbupScriptComparer())
.WithFilter(new DbupScriptFilter(DatabaseType.SQLite))
.LogToConsole();
dbupBuilder.Configure(c => c.Journal = new DbupTableJournal(() => c.ConnectionManager, () => c.Log, "schemaversions"));
dbupBuilder.Configure(c => c.Journal = new DbupSQLiteTableJournal(() => c.ConnectionManager, () => c.Log, "schemaversions"));
return dbupBuilder.Build().PerformUpgrade();
}
catch (Exception ex)
{
return new DatabaseUpgradeResult(null, false, ex, null);
}
}
}
public class DbupMySqlHelper
{
public DatabaseUpgradeResult Migrate(string connectionString)
{
try
{
var dbupBuilder = DeployChanges.To
.MySqlDatabase(connectionString)
.WithScriptsEmbeddedInAssembly(Assembly.GetExecutingAssembly())
.WithScriptNameComparer(new DbupScriptComparer())
.WithFilter(new DbupScriptFilter(DatabaseType.MySQL))
.LogToConsole();
dbupBuilder.Configure(c => c.Journal = new DbupMySqlTableJournal(() => c.ConnectionManager, () => c.Log, "weddingshare", "schemaversions"));
return dbupBuilder.Build().PerformUpgrade();
}

View File

@@ -0,0 +1,136 @@
using System.Collections;
using System.Reflection;
using WeddingShare.Helpers.Database;
using WeddingShare.Models.Database;
namespace WeddingShare.Helpers.Dbup
{
public class DbupImporter(IConfigHelper config, IDatabaseHelper database, ILogger<DbupImporter> logger)
{
public async Task ImportSettings()
{
try
{
var settings = await database.GetAllSettings();
if (settings == null || !settings.Any())
{
var systemKeys = GetAllKeys();
foreach (var key in systemKeys)
{
try
{
var configVal = config.Get(key);
if (!string.IsNullOrWhiteSpace(configVal))
{
await database.AddSetting(new SettingModel()
{
Id = key,
Value = configVal
});
}
}
catch { }
}
var galleries = await database.GetAllGalleries();
if (galleries != null && galleries.Any())
{
var galleryKeys = GetKeys<Constants.Settings.Gallery>();
foreach (var gallery in galleries)
{
if (!string.IsNullOrWhiteSpace(gallery?.Name))
{
foreach (var key in galleryKeys)
{
try
{
var galleryOverride = config.GetEnvironmentVariable(key, gallery.Name);
if (!string.IsNullOrWhiteSpace(galleryOverride))
{
await database.AddSetting(new SettingModel()
{
Id = key,
Value = galleryOverride
}, gallery.Name);
}
}
catch { }
}
}
}
}
}
}
catch (Exception ex)
{
logger.LogError($"Failed to import settings at startup - {ex?.Message}", ex);
}
}
private IEnumerable<string> GetAllKeys()
{
var keys = new List<string>();
try
{
keys.AddRange(GetKeys<Constants.BackgroundServices>());
keys.AddRange(GetKeys<Constants.Notifications>());
keys.AddRange(GetKeys<Constants.Security>());
keys.AddRange(GetKeys<Constants.Settings>());
}
catch { }
return keys.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct();
}
private IEnumerable<string> GetKeys<T>(bool includeNesteted = true)
{
var keys = new List<string>();
try
{
var obj = Activator.CreateInstance<T>();
foreach (var val in GetConstants(typeof(T), includeNesteted))
{
keys.Add((string)(val.GetValue(obj) ?? string.Empty));
}
}
catch { }
return keys.Where(x => !string.IsNullOrWhiteSpace(x));
}
private FieldInfo[] GetConstants(Type type, bool includeNesteted)
{
var constants = new ArrayList();
try
{
if (includeNesteted)
{
var classInfos = type.GetNestedTypes(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
foreach (var ci in classInfos)
{
var consts = GetConstants(ci, includeNesteted);
if (consts != null && consts.Length > 0)
{
constants.AddRange(consts);
}
}
}
var fieldInfos = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy);
foreach (var fi in fieldInfos)
{
if (fi.IsLiteral && !fi.IsInitOnly)
{
constants.Add(fi);
}
}
}
catch { }
return (FieldInfo[])constants.ToArray(typeof(FieldInfo));
}
}
}

View File

@@ -0,0 +1,33 @@
using System.Data;
using DbUp.Engine;
using DbUp.Engine.Output;
using DbUp.Engine.Transactions;
using DbUp.MySql;
namespace WeddingShare.Helpers.Dbup
{
public class DbupMySqlTableJournal : MySqlTableJournal
{
public DbupMySqlTableJournal(Func<IConnectionManager> connectionManager, Func<IUpgradeLog> logger, string scheme, string table)
: base(connectionManager, logger, scheme, table)
{
}
public override void StoreExecutedScript(SqlScript script, Func<IDbCommand> dbCommandFactory)
{
var scriptName = script.Name;
try
{
var parts = script.Name.Split('.', StringSplitOptions.RemoveEmptyEntries);
if (parts != null && parts.Length >= 2)
{
scriptName = string.Join(".", parts.Skip(parts.Length - 2).Take(2));
}
}
catch { }
base.StoreExecutedScript(new SqlScript(scriptName, script.Contents), dbCommandFactory);
}
}
}

View File

@@ -2,13 +2,13 @@
using DbUp.Engine;
using DbUp.Engine.Output;
using DbUp.Engine.Transactions;
using DbUp.SQLite;
using DbUp.Sqlite;
namespace WeddingShare.Helpers.Dbup
{
public class DbupTableJournal : SQLiteTableJournal
public class DbupSQLiteTableJournal : SqliteTableJournal
{
public DbupTableJournal(Func<IConnectionManager> connectionManager, Func<IUpgradeLog> logger, string table)
public DbupSQLiteTableJournal(Func<IConnectionManager> connectionManager, Func<IUpgradeLog> logger, string table)
: base(connectionManager, logger, table)
{
}

View File

@@ -20,6 +20,8 @@ namespace WeddingShare.Helpers.Dbup
{
case DatabaseType.SQLite:
return scripts.Where(s => s.Name.ToLower().Contains(".sqlscripts.sqlite."));
case DatabaseType.MySQL:
return scripts.Where(s => s.Name.ToLower().Contains(".sqlscripts.mysql."));
default:
return new List<SqlScript>();
}

View File

@@ -0,0 +1,90 @@
using System.Security.Cryptography;
using System.Text;
using WeddingShare.Constants;
namespace WeddingShare.Helpers
{
public interface IEncryptionHelper
{
bool IsEncryptionEnabled();
string Encrypt(string value, string? salt = null);
}
public class EncryptionHelper : IEncryptionHelper
{
private readonly HashAlgorithmName _hashType;
private readonly int _iterations;
private readonly string _key;
private readonly string _salt;
public EncryptionHelper(ISettingsHelper settings)
{
_hashType = ParseHashType(settings.GetOrDefault(Security.Encryption.HashType, "SHA256").Result);
_iterations = settings.GetOrDefault(Security.Encryption.Iterations, 1000).Result;
_key = settings.GetOrDefault(Security.Encryption.Key, string.Empty).Result;
_salt = settings.GetOrDefault(Security.Encryption.Salt, "WUtlVOvC2a6ol9M6ZidO5sJkQxYMolyasFid2Fyqvjd0uucAjYy5EsHPxdeplFRj").Result;
}
public bool IsEncryptionEnabled()
{
return !string.IsNullOrWhiteSpace(_key) && !string.IsNullOrWhiteSpace(_salt);
}
public string Encrypt(string value, string? salt = null)
{
var enabled = this.IsEncryptionEnabled();
if (enabled)
{
var clearBytes = Encoding.Unicode.GetBytes(value);
var saltBytes = Encoding.Unicode.GetBytes(salt ?? _salt);
using (var encryptor = Aes.Create())
{
var pdb = new Rfc2898DeriveBytes(_key, saltBytes, _iterations, _hashType);
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(clearBytes, 0, clearBytes.Length);
cs.Close();
}
value = Convert.ToBase64String(ms.ToArray());
}
}
}
return value;
}
private HashAlgorithmName ParseHashType(string name)
{
switch (name?.Trim()?.ToUpper())
{
case "MD5":
return HashAlgorithmName.MD5;
case "SHA1":
return HashAlgorithmName.SHA1;
case "SHA256":
return HashAlgorithmName.SHA256;
case "SHA384":
return HashAlgorithmName.SHA384;
case "SHA512":
return HashAlgorithmName.SHA512;
case "SHA3_256":
return HashAlgorithmName.SHA3_256;
case "SHA3_384":
return HashAlgorithmName.SHA3_384;
case "SHA3_512":
return HashAlgorithmName.SHA3_512;
default:
return HashAlgorithmName.SHA256;
}
}
}
}

View File

@@ -6,5 +6,7 @@
public const int Unauthorized = 401;
public const int InvalidSecretKey = 402;
public const int GalleryCreationNotAllowed = 403;
public const int GalleryLimitReached = 405;
public const int InvalidGalleryId = 406;
}
}

View File

@@ -1,4 +1,6 @@
using WeddingShare.Helpers.Database;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
namespace WeddingShare.Helpers
{
@@ -7,19 +9,28 @@ namespace WeddingShare.Helpers
bool DirectoryExists(string path);
bool CreateDirectoryIfNotExists(string path);
bool DeleteDirectoryIfExists(string path, bool recursive = true);
bool PurgeDirectory(string path);
string[] GetDirectories(string path, string pattern = "*", SearchOption searchOption = SearchOption.AllDirectories);
string[] GetFiles(string path, string pattern = "*.*", SearchOption searchOption = SearchOption.AllDirectories);
bool FileExists(string path);
long FileSize(string path);
bool DeleteFileIfExists(string path);
bool MoveFileIfExists(string source, string destination);
long GetDirectorySize(string path);
Task<byte[]> ReadAllBytes(string path);
Task SaveFile(IFormFile file, string path, FileMode mode);
}
Task<string> GetChecksum(string path);
Task<DateTime?> GetCreationDatetime(string path);
string BytesToHumanReadable(long bytes, int decimalPlaces = 0);
}
public class FileHelper : IFileHelper
{
public FileHelper()
private readonly ILogger<FileHelper> _logger;
public FileHelper(ILogger<FileHelper> logger)
{
_logger = logger;
}
public bool DirectoryExists(string path)
@@ -51,6 +62,12 @@ namespace WeddingShare.Helpers
return false;
}
public bool PurgeDirectory(string path)
{
DeleteDirectoryIfExists(path);
return CreateDirectoryIfNotExists(path);
}
public string[] GetDirectories(string path, string pattern = "*", SearchOption searchOption = SearchOption.AllDirectories)
{
return Directory.GetDirectories(path, pattern, searchOption);
@@ -66,6 +83,11 @@ namespace WeddingShare.Helpers
return File.Exists(path);
}
public long FileSize(string path)
{
return new FileInfo(path).Length;
}
public bool DeleteFileIfExists(string path)
{
if (FileExists(path))
@@ -90,6 +112,28 @@ namespace WeddingShare.Helpers
return false;
}
public long GetDirectorySize(string path)
{
long size = 0;
if (DirectoryExists(path))
{
var info = new DirectoryInfo(path);
foreach (var file in info.GetFiles())
{
size += file.Length;
}
foreach (var dir in info.GetDirectories())
{
size += GetDirectorySize(dir.FullName);
}
}
return size;
}
public async Task<byte[]> ReadAllBytes(string path)
{
return await File.ReadAllBytesAsync(path);
@@ -102,5 +146,70 @@ namespace WeddingShare.Helpers
await file.CopyToAsync(fs);
}
}
public async Task<string> GetChecksum(string path)
{
return await Task.Run(() =>
{
var checksum = string.Empty;
try
{
using (var md5 = MD5.Create())
using (var stream = File.OpenRead(path))
{
checksum = Encoding.UTF8.GetString(md5.ComputeHash(stream));
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to compute MD5 checksum for file '{path}'");
}
return checksum;
});
}
public async Task<DateTime?> GetCreationDatetime(string path)
{
return await Task.Run(() =>
{
try
{
return new FileInfo(path).CreationTimeUtc;
}
catch
{
return DateTime.UtcNow;
}
});
}
public string BytesToHumanReadable(long bytes, int decimalPlaces = 0)
{
var sizes = new string[] { "B", "KB", "MB", "GB", "TB", "PB", "EB" };
var place = 0;
var total = 0.0;
var decimalFormat = "###0.";
for (var i = 0; i < decimalPlaces; i++)
{
decimalFormat += "0";
}
if (bytes >= 0)
{
try
{
long b = Math.Abs(bytes);
place = Convert.ToInt32(Math.Floor(Math.Log(b ,1000)));
double num = Math.Round(b / Math.Pow(1000, place), 2);
total = Math.Sign(bytes) * num;
}
catch { }
}
return total.ToString($"{decimalFormat.TrimEnd('.')} {sizes[place]}");
}
}
}

View File

@@ -1,77 +1,150 @@
using SixLabors.ImageSharp;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Localization;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Processing;
using WeddingShare.Enums;
using Xabe.FFmpeg;
using Xabe.FFmpeg.Downloader;
namespace WeddingShare.Helpers
{
public interface IImageHelper
{
Task<bool> GenerateThumbnail(string imagePath, string savePath, int size = 720);
Task<bool> GenerateThumbnail(string filePath, string savePath, int size = 720);
Task<ImageOrientation> GetOrientation(string path);
ImageOrientation GetOrientation(Image img);
MediaType GetMediaType(string filePath);
Task<bool> DownloadFFMPEG(string path);
}
public class ImageHelper : IImageHelper
{
private readonly IFileHelper _fileHelper;
private readonly ILogger _logger;
private readonly IStringLocalizer<Lang.Translations> _localizer;
public ImageHelper(IFileHelper fileHelper, ILogger<ImageHelper> logger)
private static bool FfmpegInstalled = false;
public ImageHelper(IFileHelper fileHelper, ILogger<ImageHelper> logger, IStringLocalizer<Lang.Translations> localizer)
{
_fileHelper = fileHelper;
_logger = logger;
_localizer = localizer;
}
public async Task<bool> GenerateThumbnail(string imagePath, string savePath, int size = 720)
public async Task<bool> GenerateThumbnail(string filePath, string savePath, int size = 720)
{
if (_fileHelper.FileExists(imagePath))
if (_fileHelper.FileExists(filePath))
{
try
{
var filename = Path.GetFileName(imagePath);
using (var img = await Image.LoadAsync(imagePath))
var mediaType = GetMediaType(filePath);
if (mediaType == MediaType.Image || mediaType == MediaType.Video)
{
var width = 0;
var height = 0;
var filename = Path.GetFileName(filePath);
var orientation = this.GetOrientation(img);
if (orientation == ImageOrientation.Square)
if (mediaType == MediaType.Video)
{
width = size;
height = size;
}
else if (orientation == ImageOrientation.Landscape)
{
var scale = (decimal)size / (decimal)img.Width;
width = (int)((decimal)img.Width * scale);
height = (int)((decimal)img.Height * scale);
}
else if (orientation == ImageOrientation.Portrait)
{
var scale = (decimal)size / (decimal)img.Height;
width = (int)((decimal)img.Width * scale);
height = (int)((decimal)img.Height * scale);
if (FfmpegInstalled == false)
{
_logger.LogWarning(_localizer["FFMPEG_Downloading"].Value);
return false;
}
var conversion = await FFmpeg.Conversions.FromSnippet.Snapshot(filePath, savePath, TimeSpan.FromSeconds(0));
await conversion.Start();
filePath = savePath;
}
img.Mutate(x =>
using (var img = await Image.LoadAsync(filePath))
{
x.Resize(width, height);
x.AutoOrient();
});
var width = 0;
var height = 0;
await img.SaveAsWebpAsync(savePath);
var orientation = this.GetOrientation(img);
if (orientation == ImageOrientation.Square)
{
width = size;
height = size;
}
else if (orientation == ImageOrientation.Landscape)
{
var scale = (decimal)size / (decimal)img.Width;
width = (int)((decimal)img.Width * scale);
height = (int)((decimal)img.Height * scale);
}
else if (orientation == ImageOrientation.Portrait)
{
var scale = (decimal)size / (decimal)img.Height;
width = (int)((decimal)img.Width * scale);
height = (int)((decimal)img.Height * scale);
}
img.Mutate(x =>
{
x.Resize(width, height);
x.AutoOrient();
});
await img.SaveAsWebpAsync(savePath);
}
}
return true;
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to generate thumbnail - '{imagePath}'");
_logger.LogWarning(ex, $"Failed to generate thumbnail - '{filePath}'");
}
}
return false;
}
public MediaType GetMediaType(string path)
{
try
{
var provider = new FileExtensionContentTypeProvider();
if (provider.TryGetContentType(path, out string? contentType))
{
if (contentType.StartsWith("image/", StringComparison.OrdinalIgnoreCase))
{
return MediaType.Image;
}
else if (contentType.StartsWith("video/", StringComparison.OrdinalIgnoreCase))
{
return MediaType.Video;
}
}
}
catch { }
return MediaType.Unknown;
}
public async Task<ImageOrientation> GetOrientation(string path)
{
var orientation = ImageOrientation.None;
if (_fileHelper.FileExists(path))
{
try
{
using (var img = await Image.LoadAsync(path))
{
orientation = this.GetOrientation(img);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to get image orientation- '{path}'");
}
}
return orientation;
}
public ImageOrientation GetOrientation(Image img)
{
if (img != null)
@@ -92,5 +165,26 @@ namespace WeddingShare.Helpers
return ImageOrientation.None;
}
public async Task<bool> DownloadFFMPEG(string path)
{
try
{
if (!_fileHelper.DirectoryExists(path))
{
_fileHelper.CreateDirectoryIfNotExists(path);
await FFmpegDownloader.GetLatestVersion(FFmpegVersion.Official, path);
}
FFmpeg.SetExecutablesPath(path);
FfmpegInstalled = true;
return true;
}
catch
{
return false;
}
}
}
}

View File

@@ -0,0 +1,50 @@
using System.Globalization;
namespace WeddingShare.Helpers
{
public interface ILanguageHelper
{
public List<CultureInfo> DetectSupportedCultures();
public Task<List<CultureInfo>> DetectSupportedCulturesAsync();
}
public class LanguageHelper : ILanguageHelper
{
public List<CultureInfo> DetectSupportedCultures()
{
var supportedCultures = new List<CultureInfo>();
try
{
var resourceFiles = Directory.GetFiles(Path.Combine("Resources", "Lang"), "*.resx");
var detectedCultures = resourceFiles
.Select(x => Path.GetFileNameWithoutExtension(x))
.Where(x => x.Contains("."))
.Select(x => x.Split('.').LastOrDefault());
foreach (var detectedCulture in detectedCultures)
{
if (!string.IsNullOrWhiteSpace(detectedCulture))
{
try
{
supportedCultures.Add(new CultureInfo(detectedCulture));
}
catch { }
}
}
}
catch
{
supportedCultures.Add(new CultureInfo("en-GB"));
}
return supportedCultures;
}
public Task<List<CultureInfo>> DetectSupportedCulturesAsync()
{
return Task.Run(DetectSupportedCultures);
}
}
}

View File

@@ -1,59 +1,58 @@
using System.Net;
using System.Net.Mail;
using System.Text;
namespace WeddingShare.Helpers.Notifications
{
public class EmailHelper : INotificationHelper
{
private readonly IConfigHelper _config;
private readonly ISettingsHelper _settings;
private readonly ISmtpClientWrapper _client;
private readonly ILogger _logger;
public EmailHelper(IConfigHelper config, ISmtpClientWrapper client, ILogger<EmailHelper> logger)
public EmailHelper(ISettingsHelper settings, ISmtpClientWrapper client, ILogger<EmailHelper> logger)
{
_config = config;
_settings = settings;
_client = client;
_logger = logger;
}
public async Task<bool> Send(string title, string message, string? actionLink = null)
{
if (_config.GetOrDefault("Notifications", "Smtp", "Enabled", false))
if (await _settings.GetOrDefault(Constants.Notifications.Smtp.Enabled, false))
{
try
{
var recipients = _config.GetOrDefault("Notifications", "Smtp", "Recipient", string.Empty)?.Split(new char[] { ';', ',' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)?.Select(x => new MailAddress(x));
var recipients = (await _settings.GetOrDefault(Constants.Notifications.Smtp.Recipient, string.Empty))?.Split(new char[] { ';', ',' }, StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries)?.Select(x => new MailAddress(x));
if (recipients != null && recipients.Any())
{
var host = _config.GetOrDefault("Notifications", "Smtp", "Host", string.Empty);
var host = await _settings.GetOrDefault(Constants.Notifications.Smtp.Host, string.Empty);
if (!string.IsNullOrWhiteSpace(host))
{
var port = _config.GetOrDefault("Notifications", "Smtp", "Port", 587);
var port = await _settings.GetOrDefault(Constants.Notifications.Smtp.Port, 587);
if (port > 0)
{
var from = _config.GetOrDefault("Notifications", "Smtp", "From", string.Empty);
var from = await _settings.GetOrDefault(Constants.Notifications.Smtp.From, string.Empty);
if (!string.IsNullOrWhiteSpace(from))
{
var sentToAll = true;
using (var smtp = new SmtpClient(host, port))
{
var username = _config.GetOrDefault("Notifications", "Smtp", "Username", string.Empty);
var password = _config.GetOrDefault("Notifications", "Smtp", "Password", string.Empty);
var username = await _settings.GetOrDefault(Constants.Notifications.Smtp.Username, string.Empty);
var password = await _settings.GetOrDefault(Constants.Notifications.Smtp.Password, string.Empty);
if (!string.IsNullOrWhiteSpace(username) && !string.IsNullOrWhiteSpace(password))
{
smtp.UseDefaultCredentials = false;
smtp.Credentials = new NetworkCredential(username, password);
}
smtp.EnableSsl = _config.GetOrDefault("Notifications", "Smtp", "Use_SSL", false);
smtp.EnableSsl = await _settings.GetOrDefault(Constants.Notifications.Smtp.UseSSL, false);
var sender = new MailAddress(from, _config.GetOrDefault("Notifications", "Smtp", "DisplayName", "WeddingShare"));
var sender = new MailAddress(from, await _settings.GetOrDefault(Constants.Notifications.Smtp.DisplayName, "WeddingShare"));
foreach (var to in recipients)
{
try
{
await _client.SendMailAsync(smtp, new MailMessage(new MailAddress(from, _config.GetOrDefault("Notifications", "Smtp", "DisplayName", "WeddingShare")), to)
await _client.SendMailAsync(smtp, new MailMessage(new MailAddress(from, await _settings.GetOrDefault(Constants.Notifications.Smtp.DisplayName, "WeddingShare")), to)
{
Sender = sender,
Subject = title,

View File

@@ -2,39 +2,39 @@
{
public class GotifyHelper : INotificationHelper
{
private readonly IConfigHelper _config;
private readonly ISettingsHelper _settings;
private readonly IHttpClientFactory _clientFactory;
private readonly ILogger _logger;
public GotifyHelper(IConfigHelper config, IHttpClientFactory clientFactory, ILogger<GotifyHelper> logger)
public GotifyHelper(ISettingsHelper settings, IHttpClientFactory clientFactory, ILogger<GotifyHelper> logger)
{
_config = config;
_settings = settings;
_clientFactory = clientFactory;
_logger = logger;
}
public async Task<bool> Send(string title, string message, string? actionLink = null)
{
if (string.IsNullOrWhiteSpace(_config.GetOrDefault("Notifications", "Gotify", "Endpoint", string.Empty)))
if (string.IsNullOrWhiteSpace(await _settings.GetOrDefault(Constants.Notifications.Gotify.Endpoint, string.Empty)))
{
_logger.LogWarning($"Invalid Gotify endpoint specified");
return false;
}
if (string.IsNullOrWhiteSpace(_config.GetOrDefault("Notifications", "Gotify", "Token", string.Empty)))
if (string.IsNullOrWhiteSpace(await _settings.GetOrDefault(Constants.Notifications.Gotify.Token, string.Empty)))
{
_logger.LogWarning($"Invalid Gotify token specified");
return false;
}
if (_config.GetOrDefault("Notifications", "Gotify", "Enabled", false))
if (await _settings.GetOrDefault(Constants.Notifications.Gotify.Enabled, false))
{
try
{
var token = _config.GetOrDefault("Notifications", "Gotify", "Token", string.Empty);
var token = await _settings.GetOrDefault(Constants.Notifications.Gotify.Token, string.Empty);
if (!string.IsNullOrWhiteSpace(token))
{
var priority = _config.GetOrDefault("Notifications", "Gotify", "Priority", 4);
var priority = await _settings.GetOrDefault(Constants.Notifications.Gotify.Priority, 4);
if (priority > 0)
{
message = !string.IsNullOrWhiteSpace(actionLink) ? $"{message} - Visit - {actionLink}" : message;

View File

@@ -2,14 +2,14 @@
{
public class NotificationBroker : INotificationHelper
{
private readonly IConfigHelper _config;
private readonly ISettingsHelper _settings;
private readonly ISmtpClientWrapper _smtp;
private readonly IHttpClientFactory _clientFactory;
private readonly ILoggerFactory _logger;
public NotificationBroker(IConfigHelper config, ISmtpClientWrapper smtp, IHttpClientFactory clientFactory, ILoggerFactory logger)
public NotificationBroker(ISettingsHelper settings, ISmtpClientWrapper smtp, IHttpClientFactory clientFactory, ILoggerFactory logger)
{
_config = config;
_settings = settings;
_smtp = smtp;
_clientFactory = clientFactory;
_logger = logger;
@@ -21,19 +21,19 @@
var ntfySent = true;
var gotifySent = true;
if (_config.GetOrDefault("Notifications", "Smtp", "Enabled", false))
if (await _settings.GetOrDefault(Constants.Notifications.Smtp.Enabled, false))
{
emailSent = await new EmailHelper(_config, _smtp, _logger.CreateLogger<EmailHelper>()).Send(title, message, actionLink);
emailSent = await new EmailHelper(_settings, _smtp, _logger.CreateLogger<EmailHelper>()).Send(title, message, actionLink);
}
if (_config.GetOrDefault("Notifications", "Ntfy", "Enabled", false))
if (await _settings.GetOrDefault(Constants.Notifications.Ntfy.Enabled, false))
{
ntfySent = await new NtfyHelper(_config, _clientFactory, _logger.CreateLogger<NtfyHelper>()).Send(title, message, actionLink);
ntfySent = await new NtfyHelper(_settings, _clientFactory, _logger.CreateLogger<NtfyHelper>()).Send(title, message, actionLink);
}
if (_config.GetOrDefault("Notifications", "Gotify", "Enabled", false))
if (await _settings.GetOrDefault(Constants.Notifications.Gotify.Enabled, false))
{
gotifySent = await new GotifyHelper(_config, _clientFactory, _logger.CreateLogger<GotifyHelper>()).Send(title, message, actionLink);
gotifySent = await new GotifyHelper(_settings, _clientFactory, _logger.CreateLogger<GotifyHelper>()).Send(title, message, actionLink);
}
return emailSent && ntfySent && gotifySent;

View File

@@ -1,44 +1,46 @@
namespace WeddingShare.Helpers.Notifications
using WeddingShare.Constants;
namespace WeddingShare.Helpers.Notifications
{
public class NtfyHelper : INotificationHelper
{
private readonly IConfigHelper _config;
private readonly ISettingsHelper _settings;
private readonly IHttpClientFactory _clientFactory;
private readonly ILogger _logger;
public NtfyHelper(IConfigHelper config, IHttpClientFactory clientFactory, ILogger<NtfyHelper> logger)
public NtfyHelper(ISettingsHelper settings, IHttpClientFactory clientFactory, ILogger<NtfyHelper> logger)
{
_config = config;
_settings = settings;
_clientFactory = clientFactory;
_logger = logger;
}
public async Task<bool> Send(string title, string message, string? actionLink = null)
{
if (string.IsNullOrWhiteSpace(_config.GetOrDefault("Notifications", "Ntfy", "Endpoint", string.Empty)))
if (string.IsNullOrWhiteSpace(await _settings.GetOrDefault(Constants.Notifications.Ntfy.Endpoint, string.Empty)))
{
_logger.LogWarning($"Invalid Ntfy endpoint specified");
return false;
}
if (string.IsNullOrWhiteSpace(_config.GetOrDefault("Notifications", "Ntfy", "Token", string.Empty)))
if (string.IsNullOrWhiteSpace(await _settings.GetOrDefault(Constants.Notifications.Ntfy.Token, string.Empty)))
{
_logger.LogWarning($"Invalid Ntfy token specified");
return false;
}
if (_config.GetOrDefault("Notifications", "Ntfy", "Enabled", false))
if (await _settings.GetOrDefault(Constants.Notifications.Ntfy.Enabled, false))
{
try
{
var topic = _config.GetOrDefault("Notifications", "Ntfy", "Topic", "WeddingShare");
var topic = await _settings.GetOrDefault(Constants.Notifications.Ntfy.Topic, "WeddingShare");
if (!string.IsNullOrWhiteSpace(topic))
{
var priority = _config.GetOrDefault("Notifications", "Ntfy", "Priority", 4);
var priority = await _settings.GetOrDefault(Constants.Notifications.Ntfy.Priority, 4);
if (priority > 0)
{
var defaultIcon = "https://github.com/Cirx08/WeddingShare/blob/main/WeddingShare/wwwroot/images/logo.png?raw=true";
var icon = _config.GetOrDefault("Settings", "Logo", defaultIcon);
var icon = await _settings.GetOrDefault(Settings.Basic.Logo, defaultIcon);
icon = !icon.StartsWith('.') && !icon.StartsWith('/') ? icon : defaultIcon;
var client = _clientFactory.CreateClient("NtfyClient");

View File

@@ -1,43 +0,0 @@
using WeddingShare.Helpers.Database;
namespace WeddingShare.Helpers
{
public interface ISecretKeyHelper
{
Task<string?> GetGallerySecretKey(string galleryId);
}
public class SecretKeyHelper : ISecretKeyHelper
{
private readonly IConfigHelper _config;
private readonly IDatabaseHelper _database;
public SecretKeyHelper(IConfigHelper config, IDatabaseHelper database)
{
_config = config;
_database = database;
}
public async Task<string?> GetGallerySecretKey(string galleryId)
{
try
{
var secretKey = _config.Get("Settings", $"Secret_Key_{galleryId}");
if (string.IsNullOrWhiteSpace(secretKey))
{
secretKey = (await _database.GetGallery(galleryId))?.SecretKey;
if (string.IsNullOrWhiteSpace(secretKey))
{
secretKey = _config.Get("Settings", "Secret_Key");
}
}
return secretKey;
}
catch
{
return null;
}
}
}
}

View File

@@ -0,0 +1,201 @@
using WeddingShare.Helpers.Database;
using WeddingShare.Models.Database;
namespace WeddingShare.Helpers
{
public interface ISettingsHelper
{
Task<SettingModel?> Get(string key, string? gallery = "");
Task<string> GetOrDefault(string key, string defaultValue, string? gallery = "");
Task<int> GetOrDefault(string key, int defaultValue, string? gallery = "");
Task<long> GetOrDefault(string key, long defaultValue, string? gallery = "");
Task<decimal> GetOrDefault(string key, decimal defaultValue, string? gallery = "");
Task<double> GetOrDefault(string key, double defaultValue, string? gallery = "");
Task<bool> GetOrDefault(string key, bool defaultValue, string? gallery = "");
Task<DateTime?> GetOrDefault(string key, DateTime? defaultValue, string? gallery = "");
Task<SettingModel?> SetSetting(string key, string value, string? gallery = "");
Task<bool> DeleteSetting(string key, string? gallery = "");
}
public class SettingsHelper : ISettingsHelper
{
private readonly IDatabaseHelper _databaseHelper;
private readonly IConfigHelper _configHelper;
private readonly ILogger _logger;
public SettingsHelper(IDatabaseHelper databaseHelper, IConfigHelper configHelper, ILogger<SettingsHelper> logger)
{
_databaseHelper = databaseHelper;
_configHelper = configHelper;
_logger = logger;
}
public async Task<SettingModel?> Get(string key, string? gallery = "")
{
if (!string.IsNullOrWhiteSpace(key))
{
try
{
var dbValue = await _databaseHelper.GetSetting(key, gallery);
if (dbValue != null)
{
return dbValue;
}
var configValue = _configHelper.Get(key);
if (configValue != null)
{
return new SettingModel()
{
Id = key.ToUpper(),
Value = configValue
};
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"Failed to find key '{key}' in either database or config");
}
}
return null;
}
public async Task<string> GetOrDefault(string key, string defaultValue, string? gallery = "")
{
try
{
var value = (await this.Get(key, gallery))?.Value;
if (!string.IsNullOrWhiteSpace(value))
{
return value;
}
}
catch { }
return defaultValue;
}
public async Task<int> GetOrDefault(string key, int defaultValue, string? gallery = "")
{
try
{
var value = await this.GetOrDefault(key, string.Empty, gallery);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToInt32(value);
}
}
catch { }
return defaultValue;
}
public async Task<long> GetOrDefault(string key, long defaultValue, string? gallery = "")
{
try
{
var value = await this.GetOrDefault(key, string.Empty, gallery);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToInt64(value);
}
}
catch { }
return defaultValue;
}
public async Task<decimal> GetOrDefault(string key, decimal defaultValue, string? gallery = "")
{
try
{
var value = await this.GetOrDefault(key, string.Empty, gallery);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToDecimal(value);
}
}
catch { }
return defaultValue;
}
public async Task<double> GetOrDefault(string key, double defaultValue, string? gallery = "")
{
try
{
var value = await this.GetOrDefault(key, string.Empty, gallery);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToDouble(value);
}
}
catch { }
return defaultValue;
}
public async Task<bool> GetOrDefault(string key, bool defaultValue, string? gallery = "")
{
try
{
var value = await this.GetOrDefault(key, string.Empty, gallery);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToBoolean(value);
}
}
catch { }
return defaultValue;
}
public async Task<DateTime?> GetOrDefault(string key, DateTime? defaultValue, string? gallery = "")
{
try
{
var value = await this.GetOrDefault(key, string.Empty, gallery);
if (!string.IsNullOrWhiteSpace(value))
{
return Convert.ToDateTime(value);
}
}
catch { }
return defaultValue;
}
public async Task<SettingModel?> SetSetting(string key, string value, string? gallery = "")
{
if (!string.IsNullOrWhiteSpace(key))
{
return await _databaseHelper.SetSetting(new SettingModel()
{
Id = key,
Value = value
}, gallery);
}
return null;
}
public async Task<bool> DeleteSetting(string key, string? gallery = "")
{
if (!string.IsNullOrWhiteSpace(key))
{
return await _databaseHelper.DeleteSetting(new SettingModel()
{
Id = key.ToUpper()
}, gallery);
}
return false;
}
public async Task<bool> DeleteAllSettings(string? gallery = "")
{
return await _databaseHelper.DeleteAllSettings(gallery);
}
}
}

View File

@@ -1,22 +1,94 @@
using System.Text.RegularExpressions;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using WeddingShare.Constants;
namespace WeddingShare.Helpers
{
public class UrlHelper
public interface IUrlHelper
{
public static string Generate(HttpContext? ctx, IConfigHelper config, string? append)
public string GenerateFullUrl(HttpRequest? ctx, string? path, List<KeyValuePair<string, string>>? append = null, List<string>? exclude = null);
public string GenerateBaseUrl(HttpRequest? ctx, string? path);
public string GenerateQueryString(HttpRequest? ctx, List<KeyValuePair<string, string>>? append = null, List<string>? exclude = null);
public string ExtractHost(string value);
public string ExtractQueryValue(HttpRequest? ctx, string key);
}
public class UrlHelper : IUrlHelper
{
private readonly ISettingsHelper _settings;
public UrlHelper(ISettingsHelper settings)
{
return Generate(ctx?.Request, config, append);
_settings = settings;
}
public static string Generate(HttpRequest? ctx, IConfigHelper config, string? append)
public string GenerateFullUrl(HttpRequest? ctx, string? path, List<KeyValuePair<string, string>>? append = null, List<string>? exclude = null)
{
return $"{GenerateBaseUrl(ctx, path)}{GenerateQueryString(ctx, append, exclude)}";
}
public string GenerateBaseUrl(HttpRequest? ctx, string? path)
{
if (ctx != null)
{
var scheme = config.GetOrDefault("Settings", "Force_Https", false) ? "https" : ctx.Scheme;
var host = Regex.Replace(config.GetOrDefault("Settings", "Base_Url", ctx.Host.Value), "http[s]*\\:\\/\\/", string.Empty).TrimEnd('/');
var scheme = _settings.GetOrDefault(Settings.Basic.ForceHttps, false).Result ? "https" : ctx.Scheme;
var host = ExtractHost(_settings.GetOrDefault(Settings.Basic.BaseUrl, ctx.Host.Value).Result);
return $"{scheme}://{host}/{append?.TrimStart('/')}";
return $"{scheme}://{host}/{path?.TrimStart('/')}";
}
return string.Empty;
}
public string GenerateQueryString(HttpRequest? ctx, List<KeyValuePair<string, string>>? append = null, List<string>? exclude = null)
{
if (ctx != null)
{
append = append ?? new List<KeyValuePair<string, string>>();
exclude = exclude ?? new List<string>();
foreach (var a in append)
{
exclude.Add(a.Key);
}
var queryString = new StringBuilder();
foreach (var q in ctx.Query.Where(x => !exclude.Any(f => f.Equals(x.Key, StringComparison.OrdinalIgnoreCase))))
{
queryString.Append($"&{HttpUtility.UrlEncode(q.Key)}={HttpUtility.UrlEncode(q.Value)}");
}
foreach (var a in append)
{
queryString.Append($"&{HttpUtility.UrlEncode(a.Key)}={HttpUtility.UrlEncode(a.Value)}");
}
return $"?{queryString.ToString().Trim('&')}".TrimEnd('?');
}
return string.Empty;
}
public string ExtractHost(string value)
{
if (!string.IsNullOrWhiteSpace(value))
{
return Regex.Replace(value, "http[s]*\\:\\/\\/", string.Empty).Trim('/');
}
return string.Empty;
}
public string ExtractQueryValue(HttpRequest? ctx, string key)
{
if (ctx?.Query != null)
{
try
{
return ctx.Query.FirstOrDefault(x => string.Equals(key, x.Key, StringComparison.OrdinalIgnoreCase)).Value.ToString();
}
catch { }
}
return string.Empty;

View File

@@ -0,0 +1,6 @@
namespace WeddingShare.Lang
{
public class Translations
{
}
}

View File

@@ -5,23 +5,35 @@ namespace WeddingShare.Models.Database
public class GalleryItemModel
{
public GalleryItemModel()
: this(0, 0, string.Empty, null, GalleryItemState.Pending)
: this(0, 0, string.Empty, string.Empty, null, null, null, MediaType.Unknown, ImageOrientation.None, GalleryItemState.Pending, 0)
{
}
public GalleryItemModel(int id, int galleryId, string title, string? uploadedBy, GalleryItemState state)
public GalleryItemModel(int id, int galleryId, string galleryName, string title, string? uploadedBy, DateTime? uploadedDate, string? checksum, MediaType mediaType, ImageOrientation orientation, GalleryItemState state, long file_size)
{
Id = id;
GalleryId = galleryId;
GalleryName = galleryName;
Title = title;
UploadedBy = uploadedBy;
UploadedDate = uploadedDate;
Checksum = checksum;
MediaType = mediaType;
Orientation = orientation;
State = state;
FileSize = file_size;
}
public int Id { get; set; }
public int GalleryId { get; set; }
public string GalleryName { get; set; }
public string Title { get; set; }
public string? UploadedBy { get; set; }
public DateTime? UploadedDate { get; set; }
public string? Checksum { get; set; }
public MediaType MediaType { get; set; }
public ImageOrientation Orientation { get; set; }
public GalleryItemState State { get; set; }
public long FileSize { get; set; }
}
}

View File

@@ -8,5 +8,11 @@
public int TotalItems { get; set; }
public int ApprovedItems { get; set; }
public int PendingItems { get; set; }
public long TotalGallerySize { get; set; }
public string CalculateUsage(long maxSizeMB = long.MaxValue)
{
return ((double)(TotalGallerySize / (double)(maxSizeMB * 1000000L))).ToString("0.00%");
}
}
}

View File

@@ -1,7 +0,0 @@
namespace WeddingShare.Models.Database
{
public class PendingGalleryItemModel : GalleryItemModel
{
public string GalleryName { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
namespace WeddingShare.Models.Database
{
public class SettingModel
{
public string Id { get; set; }
public string? Value { get; set; }
public T Parse<T>(T defaultValue)
{
if (!string.IsNullOrWhiteSpace(this.Value))
{
try
{
return (T)Convert.ChangeType(this.Value, typeof(T));
}
catch { }
}
return defaultValue;
}
}
}

View File

@@ -6,8 +6,10 @@
public string Username { get; set; }
public string? Email { get; set; }
public string? Password { get; set; }
public string? CPassword { get; set; }
public int FailedLogins { get; set; }
public DateTime? LockoutUntil { get; set; }
public string? MultiFactorToken { get; set; }
public bool IsLockedOut
{

View File

@@ -0,0 +1,12 @@
namespace WeddingShare.Models
{
public class ExportOptions
{
public bool Database { get; set; } = true;
public bool Uploads { get; set; } = true;
public bool Thumbnails { get; set; } = true;
public bool Logos { get; set; } = true;
public bool Banners { get; set; } = true;
public bool CustomResources { get; set; } = true;
}
}

View File

@@ -2,15 +2,17 @@
{
public class FileUploader
{
public FileUploader(string id, string? key, string url)
public FileUploader(string id, string? key, string url, bool identityRequired = false)
{
this.GalleryId = id;
this.SecretKey = key;
this.UploadUrl = url;
this.IdentityRequired = identityRequired;
}
public string? GalleryId { get; set; }
public string? SecretKey { get; set; }
public string? UploadUrl { get; set; }
public bool IdentityRequired { get; set; }
}
}

View File

@@ -6,9 +6,11 @@
{
this.Username = string.Empty;
this.Password = string.Empty;
this.Code = string.Empty;
}
public string Username { get; set; }
public string Password { get; set; }
public string Code { get; set; }
}
}

View File

@@ -0,0 +1,16 @@
namespace WeddingShare.Models.Migrator
{
public class KeyMigrator
{
public KeyMigrator(int priority, string key, Func<string, string>? action = null)
{
Priority = priority;
Key = key;
MigrationAction = action;
}
public string Key { get; set; }
public Func<string, string>? MigrationAction { get; set; }
public int Priority { get; set; }
}
}

View File

@@ -5,38 +5,38 @@ namespace WeddingShare.Models
public class PhotoGallery
{
public PhotoGallery()
: this(ViewMode.Default)
: this(ViewMode.Default, GalleryGroup.None, GalleryOrder.Descending)
{
}
public PhotoGallery(ViewMode viewMode)
: this("default", string.Empty, string.Empty, string.Empty, viewMode, new List<PhotoGalleryImage>())
public PhotoGallery(ViewMode viewMode, GalleryGroup groupBy, GalleryOrder orderBy)
: this(1, "default", string.Empty, viewMode, groupBy, orderBy, new List<PhotoGalleryImage>(), false)
{
}
public PhotoGallery(string id, string secretKey, string galleryPath, string thumbnailPath, ViewMode viewMode, List<PhotoGalleryImage> images)
public PhotoGallery(int id, string name, string secretKey, ViewMode viewMode, GalleryGroup groupBy, GalleryOrder orderBy, List<PhotoGalleryImage> images, bool requireIdentity)
{
this.GalleryId = id;
this.GalleryPath = galleryPath;
this.ThumbnailsPath = thumbnailPath;
this.GalleryName = name;
this.ViewMode = viewMode;
this.GroupBy = groupBy;
this.OrderBy = orderBy;
this.PendingCount = 0;
this.Images = images;
this.FileUploader = new FileUploader(id, secretKey, "/Gallery/UploadImage");
this.FileUploader = new FileUploader(name, secretKey, "/Gallery/UploadImage", requireIdentity);
}
public string? GalleryId { get; set; }
public string? GalleryPath { get; set; }
public string? ThumbnailsPath { get; set; }
public int? GalleryId { get; set; }
public string? GalleryName { get; set; }
public ViewMode ViewMode { get; set; }
public GalleryGroup GroupBy { get; set; }
public GalleryOrder OrderBy { get; set; }
public int ApprovedCount { get; set; }
public int PendingCount { get; set; }
public int ApprovedCount
{
get
{
return this.Images?.Count ?? 0;
}
}
public int ItemsPerPage { get; set; } = 50;
public int CurrentPage { get; set; } = 1;
public bool Pagination { get; set; } = true;
public bool LoadScripts { get; set; } = true;
public int TotalCount
{
get
@@ -55,8 +55,13 @@ namespace WeddingShare.Models
}
public int Id { get; set; }
public string? Path { get; set; }
public int? GalleryId { get; set; }
public string? GalleryName { get; set; }
public string? Name { get; set; }
public string? UploadedBy { get; set; }
public DateTime? UploadDate { get; set; }
public string? ImagePath { get; set; }
public string? ThumbnailPath { get; set; }
public MediaType MediaType { get; set; }
}
}

View File

@@ -0,0 +1,12 @@
namespace WeddingShare.Models
{
public static class SessionKey
{
public const string DeviceType = "DeviceType";
public const string ViewerIdentity = "ViewerIdentity";
public const string MultiFactorTokenSet = "2FA_SET";
public const string MultiFactorSecret = "2FA_SECRET";
public const string MultiFactorQR = "2FA_QR_CODE";
public const string SelectedLanguage = "SelectedLanguage";
}
}

View File

@@ -0,0 +1,9 @@
namespace WeddingShare.Models
{
public class SupportedLanguage
{
public string Key { get; set; }
public string Value { get; set; }
public bool Selected { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System.Runtime.Serialization;
namespace WeddingShare.Models
{
public class UpdateSettingsModel
{
[DataMember(Name = "key")]
public string Key { get; set; }
[DataMember(Name = "value")]
public string? Value { get; set; }
}
}

View File

@@ -16,6 +16,6 @@ namespace WeddingShare
.UseIIS()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseUrls("http://*:5000/");
.UseUrls("http://*:5000");
}
}

View File

@@ -14,8 +14,8 @@
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"environmentVariables": {
"ASPNETCORE_HTTPS_PORTS": "8081",
"ASPNETCORE_HTTP_PORTS": "8080"
"ASPNETCORE_HTTPS_PORTS": "5001",
"ASPNETCORE_HTTP_PORTS": "5000"
},
"publishAllPorts": true,
"useSSL": true

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>Failed to create gallery</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>Failed to delete gallery</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>Failed to download gallery</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>Failed to update gallery</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>Failed to export data</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>Failed to find file</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>Failed to import data</value>
</data>
<data name="Failed_Reviewing_Photo" xml:space="preserve">
<value>Failed to review photo</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>Failed to wipe galleries</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>Failed to wipe gallery</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>Gallery name already exists</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>Faied to log user in</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>Gallery name cannot be empty</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>Failed to get pending uploads</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>Unknown review action</value>
</data>
</root>

View File

@@ -1,165 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>Impossible de créer la galerie</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>Impossible de supprimer la galerie</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>Impossible de télécharger la galerie</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>Échec de la mise à jour de la galerie</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>Échec de l'exportation des données</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>Impossible de trouver le fichier</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>Échec de l'importation des données</value>
</data>
<data name="Failed_Reviewing_Photo" xml:space="preserve">
<value>Impossible de vérifier la photo</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>Impossible d'effacer la galerie</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>Impossible d'effacer la galerie</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>Le nom de la galerie existe déjà</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>Impossible de connecter l'utilisateur</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>Le nom de la galerie ne peut pas être vide</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>Impossible d'obtenir les téléchargements en attente</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>Action de révision inconnue</value>
</data>
</root>

View File

@@ -1,153 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="File_Upload_Failed" xml:space="preserve">
<value>Failed to upload file</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>Failed to upload images</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>File type is invalid</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>Invalid gallery Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>Invalid gallery key</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>A request was made using an invalid secret key</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>Max file size is</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>Failed to save image to gallery</value>
</data>
</root>

View File

@@ -1,153 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="File_Upload_Failed" xml:space="preserve">
<value>Impossible de télécharger le fichier</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>La galerie demandée n'a pas été trouvée ou n'existe pas</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>Impossible de télécharger les images</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>Le jeton d'accès fourni n'était pas valide</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>Le type de fichier n'est pas valide</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>ID de galerie invalide</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>Clé de galerie non valide</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>Une demande a été effectuée à l'aide d'une clé de sécurité non valide</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>La taille maximale du fichier est</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>Aucun fichier n'a été détecté pour le téléchargement</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>Impossible d'enregistrer l'image dans la galerie</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

View File

@@ -0,0 +1,901 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="400_Error_Message" xml:space="preserve">
<value>إذا استمرت هذه المسألة، يرجى الاتصال بمدير الإنترنت</value>
</data>
<data name="400_Error_Title" xml:space="preserve">
<value>يبدو أن خطأ غير متوقع حدث.</value>
</data>
<data name="401_Error_Message" xml:space="preserve">
<value>يبدو أنك غير مصرح لك بالنظر إلى تلك الصفحة.</value>
</data>
<data name="401_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="402_Error_Message" xml:space="preserve">
<value>المفتاح السري كان غير صحيح</value>
</data>
<data name="402_Error_Title" xml:space="preserve">
<value>مفتاح سرّي غير مستقر</value>
</data>
<data name="403_Error_Message" xml:space="preserve">
<value>المجوهرات المحددة لا وجود لها وليس لديك إذن لخلقها</value>
</data>
<data name="403_Error_Title" xml:space="preserve">
<value>Access Denied</value>
</data>
<data name="405_Error_Message" xml:space="preserve">
<value>لقد وصلت إلى الحد الأقصى لخلق المعرض</value>
</data>
<data name="405_Error_Title" xml:space="preserve">
<value>Limit Reached</value>
</data>
<data name="406_Error_Message" xml:space="preserve">
<value>المعرض المحدد لا يوجد</value>
</data>
<data name="406_Error_Title" xml:space="preserve">
<value>جالري</value>
</data>
<data name="Actions" xml:space="preserve">
<value>الإجراءات</value>
</data>
<data name="Add" xml:space="preserve">
<value>مضافا إليها</value>
</data>
<data name="Approve" xml:space="preserve">
<value>الموافقة</value>
</data>
<data name="Approved" xml:space="preserve">
<value>الوظائف المعتمدة</value>
</data>
<data name="Available_Galleries" xml:space="preserve">
<value>المتاح</value>
</data>
<data name="Clean" xml:space="preserve">
<value>نظيفة</value>
</data>
<data name="Clean_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد تنظيف هذا المعرض؟?</value>
</data>
<data name="Click_To_Upload" xml:space="preserve">
<value>انقر هنا لاختيار الملفات</value>
</data>
<data name="Close" xml:space="preserve">
<value>اقترب</value>
</data>
<data name="Create" xml:space="preserve">
<value>الإبداع</value>
</data>
<data name="Danger_Zone" xml:space="preserve">
<value>منطقة الخطر</value>
</data>
<data name="Default" xml:space="preserve">
<value>التقصير</value>
</data>
<data name="Delete" xml:space="preserve">
<value>تحذف</value>
</data>
<data name="Delete_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد حذف هذا المعرض؟?</value>
</data>
<data name="Destructive_Action_Performed" xml:space="preserve">
<value>الإجراءات المدمرة</value>
</data>
<data name="Download_Gallery_Not_Allowed" xml:space="preserve">
<value>لا يسمح لك بتحميل هذا المعرض</value>
</data>
<data name="Drag_And_Drop" xml:space="preserve">
<value>Drag</value>
</data>
<data name="Duplicate_Item_Detected" xml:space="preserve">
<value>ملف مزدوج مكتشف</value>
</data>
<data name="Export" xml:space="preserve">
<value>الصادرات</value>
</data>
<data name="Failed_Add_Gallery" xml:space="preserve">
<value>فشل في خلق المعرض</value>
</data>
<data name="Failed_Add_User" xml:space="preserve">
<value>فشل في خلق مستعمل</value>
</data>
<data name="Failed_Delete_Gallery" xml:space="preserve">
<value>فشل في حذف المعرض</value>
</data>
<data name="Failed_Delete_User" xml:space="preserve">
<value>عدم حذف المستخدم</value>
</data>
<data name="Failed_Download_Gallery" xml:space="preserve">
<value>فشل في تنزيل المعرض</value>
</data>
<data name="Failed_Edit_Gallery" xml:space="preserve">
<value>فشل في تحديث المعرض</value>
</data>
<data name="Failed_Edit_User" xml:space="preserve">
<value>لم يستكمل المستعمل</value>
</data>
<data name="Failed_Export" xml:space="preserve">
<value>الفشل في الحصول على بيانات التصدير</value>
</data>
<data name="Failed_Finding_File" xml:space="preserve">
<value>فشل في العثور على ملف</value>
</data>
<data name="Failed_Import" xml:space="preserve">
<value>الفشل في الحصول على بيانات الاستيراد</value>
</data>
<data name="Failed_Reviewing_Media" xml:space="preserve">
<value>عدم استعراض البند</value>
</data>
<data name="Failed_Update_Setting" xml:space="preserve">
<value>فشل في تحديث الأطر</value>
</data>
<data name="Failed_Wipe_Galleries" xml:space="preserve">
<value>فشل في مسح المغالي</value>
</data>
<data name="Failed_Wipe_Gallery" xml:space="preserve">
<value>فشل في محو المعرض</value>
</data>
<data name="FFMPEG_Download_Failed" xml:space="preserve">
<value>فشل في تحميل FFMPEG إلى الطريق</value>
</data>
<data name="FFMPEG_Downloading" xml:space="preserve">
<value>محاولاً توليد إبهام بالفيديو، لكن شركة FMPEG لم تستكمل التحميل بعد. إذا كانت هذه تركيبة جديدة، يرجى الانتظار لتنزيلها لإكمال وضياع الإبهام</value>
</data>
<data name="File_Upload_Failed" xml:space="preserve">
<value>فشل في رفع الملف</value>
</data>
<data name="Filter" xml:space="preserve">
<value>فيلم</value>
</data>
<data name="Galleries" xml:space="preserve">
<value>Galleries</value>
</data>
<data name="Gallery" xml:space="preserve">
<value>Gallery</value>
</data>
<data name="Gallery_Does_Not_Exist" xml:space="preserve">
<value>The requested gallery was not found or does not exist</value>
</data>
<data name="Gallery_Empty" xml:space="preserve">
<value>لا توجد حاليا ذكريات مشتركة</value>
</data>
<data name="Gallery_Empty_Upload" xml:space="preserve">
<value>لا توجد حالياً ذكريات مشتركة لماذا لا تحملين ذكرياتك!</value>
</data>
<data name="Gallery_Full" xml:space="preserve">
<value>المعرض ممتلئ وقد تجاوز الحد الأقصى لحجم</value>
</data>
<data name="Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض</value>
</data>
<data name="Gallery_Key_Help" xml:space="preserve">
<value>من فضلك أدخل المفتاح السري.</value>
</data>
<data name="Gallery_Key_Placeholder" xml:space="preserve">
<value>يوم الزفاف</value>
</data>
<data name="Gallery_Limit_Reached" xml:space="preserve">
<value>تم الوصول إلى الحد الأقصى المسموح به من المغالي</value>
</data>
<data name="Gallery_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المعرض</value>
</data>
<data name="Gallery_Name" xml:space="preserve">
<value>اسم المعرض</value>
</data>
<data name="Gallery_Name_Already_Exists" xml:space="preserve">
<value>اسم المعرض موجود بالفعل</value>
</data>
<data name="Gallery_Name_Help" xml:space="preserve">
<value>رجاءً أدخلي اسم المعرض الذي تريدين زيارته.</value>
</data>
<data name="Gallery_Name_Placeholder" xml:space="preserve">
<value>حفل زفافي</value>
</data>
<data name="Gallery_Selector_Title" xml:space="preserve">
<value>Gallery Selector</value>
</data>
<data name="Generate" xml:space="preserve">
<value>الجيل</value>
</data>
<data name="Group" xml:space="preserve">
<value>المجموعة</value>
</data>
<data name="Homepage_Load_Error" xml:space="preserve">
<value>وقع خطأ في تحميل الصفحة الرئيسية</value>
</data>
<data name="Identity_Session_Error" xml:space="preserve">
<value>عدم تحديد جلسة هوية المستخدمين - الاسم</value>
</data>
<data name="Image_Upload_Failed" xml:space="preserve">
<value>فشل في تحميل الصور</value>
</data>
<data name="Import" xml:space="preserve">
<value>الواردات</value>
</data>
<data name="Invalid_Access_Token" xml:space="preserve">
<value>The provided access token was invalid</value>
</data>
<data name="Invalid_File_Type" xml:space="preserve">
<value>نوع الملف غير صحيح</value>
</data>
<data name="Invalid_Gallery_Id" xml:space="preserve">
<value>معرض غير رسمي Id</value>
</data>
<data name="Invalid_Gallery_Key" xml:space="preserve">
<value>مفتاح المعرض الفاشل</value>
</data>
<data name="Invalid_Secret_Key_Warning" xml:space="preserve">
<value>قُدم طلب باستخدام مفتاح سري غير صحيح</value>
</data>
<data name="Key" xml:space="preserve">
<value>المفتاح</value>
</data>
<data name="Link" xml:space="preserve">
<value>Link</value>
</data>
<data name="Login" xml:space="preserve">
<value>Login</value>
</data>
<data name="Login_Failed" xml:space="preserve">
<value>فشل في تسجيل المستخدمين</value>
</data>
<data name="Login_Password_Help" xml:space="preserve">
<value>من فضلك أدخل كلمة السر.</value>
</data>
<data name="Login_Password_Placeholder" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Login_Username_Help" xml:space="preserve">
<value>من فضلك أدخل اسم المستخدم.</value>
</data>
<data name="Login_Username_Placeholder" xml:space="preserve">
<value>Admin</value>
</data>
<data name="Logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="Manage_Data" xml:space="preserve">
<value>بيانات إدارة</value>
</data>
<data name="Max_File_Size" xml:space="preserve">
<value>حجم ملف (ماكس)</value>
</data>
<data name="MultiFactor_Token_Set_Failed" xml:space="preserve">
<value>فشل في إنشاء 2FA</value>
</data>
<data name="Name" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Name_Cannot_Be_Blank" xml:space="preserve">
<value>اسم المعرض لا يمكن أن يكون فارغا</value>
</data>
<data name="New_Items_Pending_Review" xml:space="preserve">
<value>البنود الجديدة قيد الاستعراض</value>
</data>
<data name="No_Data" xml:space="preserve">
<value>لا توجد بيانات</value>
</data>
<data name="No_Files_For_Upload" xml:space="preserve">
<value>No files were detected for upload</value>
</data>
<data name="No_Galleries" xml:space="preserve">
<value>لا توجد مسابقات بعد</value>
</data>
<data name="No_Pending_Uploads" xml:space="preserve">
<value>No uploads pending review</value>
</data>
<data name="Pending" xml:space="preserve">
<value>معلقة</value>
</data>
<data name="Pending_Uploads" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Pending_Uploads_Failed" xml:space="preserve">
<value>فشل في الحصول على تحميل</value>
</data>
<data name="Reject" xml:space="preserve">
<value>Reject</value>
</data>
<data name="Rename" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Review" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Save_QR" xml:space="preserve">
<value>أنقذوا (كيو آر)</value>
</data>
<data name="Save_To_Gallery_Failed" xml:space="preserve">
<value>فشل في توفير الصور للمعرض</value>
</data>
<data name="Scan_To_Share" xml:space="preserve">
<value>قوموا بمسح شفرة (كيو آر) بهواتفكم لتشاركوا صوركم</value>
</data>
<data name="Settings" xml:space="preserve">
<value>الترتيبات</value>
</data>
<data name="Settings_Account" xml:space="preserve">
<value>الحساب</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Help" xml:space="preserve">
<value>كم عدد قطع الأشجار الفاشلة قبل أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Attempts_Label" xml:space="preserve">
<value>أعمال الضبط</value>
</data>
<data name="Settings_Account_Lockout_Mins_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يغلق المستخدم</value>
</data>
<data name="Settings_Account_Lockout_Mins_Label" xml:space="preserve">
<value>أجهزة المراقبة</value>
</data>
<data name="Settings_Account_Profile_Icon_Help" xml:space="preserve">
<value>إذا ظهرت الصورة في الرأس</value>
</data>
<data name="Settings_Account_Profile_Icon_Label" xml:space="preserve">
<value>Profile Icon</value>
</data>
<data name="Settings_Alerts" xml:space="preserve">
<value>إنذار</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Help" xml:space="preserve">
<value>يجب أن ترسل التحذيرات عندما تكون الحسابات مغلقة</value>
</data>
<data name="Settings_Alerts_Account_Lockout_Label" xml:space="preserve">
<value>حساب القفل</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات عند القيام بعمل تدميري</value>
</data>
<data name="Settings_Alerts_Destructive_Action_Label" xml:space="preserve">
<value>الأعمال المدمرة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات في محاولات قطع الأشجار الفاشلة</value>
</data>
<data name="Settings_Alerts_Failed_Login_Label" xml:space="preserve">
<value>Failed Login</value>
</data>
<data name="Settings_Alerts_Pending_Review_Help" xml:space="preserve">
<value>ينبغي إرسال الإنذارات إذا كان هناك بند جديد قيد الاستعراض</value>
</data>
<data name="Settings_Alerts_Pending_Review_Label" xml:space="preserve">
<value>استعراض انتظار</value>
</data>
<data name="Settings_BackgroundServices" xml:space="preserve">
<value>خدمات المعلومات الأساسية</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Help" xml:space="preserve">
<value>تنظيف الملفات المؤقتة</value>
</data>
<data name="Settings_BackgroundServices_Cleanup_Label" xml:space="preserve">
<value>التنظيف</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Help" xml:space="preserve">
<value>التقطت الملفات مباشرة إلى ملف المعرض</value>
</data>
<data name="Settings_BackgroundServices_DirectoryScanner_Label" xml:space="preserve">
<value>Directory Scanner</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Help" xml:space="preserve">
<value>أرسل تقرير عن حالة المعرض</value>
</data>
<data name="Settings_BackgroundServices_EmailReport_Label" xml:space="preserve">
<value>Email Reports</value>
</data>
<data name="Settings_Basic" xml:space="preserve">
<value>أساسي</value>
</data>
<data name="Settings_Basic_BaseUrl_Help" xml:space="preserve">
<value>قاعدة أورل التي ستستخدم في الوصلات والإخطارات</value>
</data>
<data name="Settings_Basic_BaseUrl_Label" xml:space="preserve">
<value>القاعدة</value>
</data>
<data name="Settings_Basic_EmailReport_Help" xml:space="preserve">
<value>هل ينبغي إرسال تقرير بريد إلكتروني مع إحصائيات المعرض</value>
</data>
<data name="Settings_Basic_EmailReport_Label" xml:space="preserve">
<value>Email Report</value>
</data>
<data name="Settings_Basic_ForceHttps_Help" xml:space="preserve">
<value>يجب أن Htp users be forced to https</value>
</data>
<data name="Settings_Basic_ForceHttps_Label" xml:space="preserve">
<value>القوة</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Help" xml:space="preserve">
<value>كيف يختار المستعملون معرضاً</value>
</data>
<data name="Settings_Basic_GallerySelectorDropdown_Label" xml:space="preserve">
<value>Gallery Selector النوع</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Help" xml:space="preserve">
<value>إذا كان خيار المعرض الافتراضي واضح</value>
</data>
<data name="Settings_Basic_GallerySelectorHideDefault_Label" xml:space="preserve">
<value>معرض الدفن</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Help" xml:space="preserve">
<value>هل يُسمح للضيوف أن يخلقوا مجالات جديدة</value>
</data>
<data name="Settings_Basic_GuestGalleryCreation_Label" xml:space="preserve">
<value>مهرجان الضيوف</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Help" xml:space="preserve">
<value>إذا أُدرج المفتاح السرّي في رمز QR</value>
</data>
<data name="Settings_Basic_HideKeyFromQRCode_Label" xml:space="preserve">
<value>Insecure QR Codes</value>
</data>
<data name="Settings_Basic_HomeLink_Help" xml:space="preserve">
<value>يَجِبُ أَنْ يَنْقرَ الرابطَ المنزليَ إعادة توجيه المستخدم</value>
</data>
<data name="Settings_Basic_HomeLink_Label" xml:space="preserve">
<value>Clickable Home Link</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Help" xml:space="preserve">
<value>يجب أن تُفتح وصلات جديدة</value>
</data>
<data name="Settings_Basic_LinksOpenNewTab_Label" xml:space="preserve">
<value>New Tab Links</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Help" xml:space="preserve">
<value>كم عدد المجرات المسموح للمستعملين بصنعها</value>
</data>
<data name="Settings_Basic_MaxGalleryCount_Label" xml:space="preserve">
<value>ماكس غاليري كونت</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Help" xml:space="preserve">
<value>يجب أن يكون هناك مهر واحد أو أكثر</value>
</data>
<data name="Settings_Basic_SingleGalleryMode_Label" xml:space="preserve">
<value>Gallery Mode</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Help" xml:space="preserve">
<value>الحد الأقصى للبكازات في أي اتجاه للإبهام</value>
</data>
<data name="Settings_Basic_ThumbnailSize_Label" xml:space="preserve">
<value>حل الإبهام</value>
</data>
<data name="Settings_Gallery" xml:space="preserve">
<value>التخلف عن الدفع</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Help" xml:space="preserve">
<value>ما هو نوع الملف المسموح به للضيوف</value>
</data>
<data name="Settings_Gallery_AllowedFileTypes_Label" xml:space="preserve">
<value>أنواع الملفات المسموح بها</value>
</data>
<data name="Settings_Gallery_BannerImage_Help" xml:space="preserve">
<value>صورة لعرضها على قمة المعرض</value>
</data>
<data name="Settings_Gallery_BannerImage_Label" xml:space="preserve">
<value>طراز Banner Image</value>
</data>
<data name="Settings_Gallery_Columns_Help" xml:space="preserve">
<value>عدد الأعمدة المعروضة</value>
</data>
<data name="Settings_Gallery_Columns_Label" xml:space="preserve">
<value>Column count</value>
</data>
<data name="Settings_Gallery_DefaultView_Help" xml:space="preserve">
<value>ما هو مشهد قافلة</value>
</data>
<data name="Settings_Gallery_DefaultView_Label" xml:space="preserve">
<value>الصورة الافتراضية</value>
</data>
<data name="Settings_Gallery_Download_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Download_Label" xml:space="preserve">
<value>السماح بتحميل</value>
</data>
<data name="Settings_Gallery_FullWidth_Help" xml:space="preserve">
<value>عرض المعرض</value>
</data>
<data name="Settings_Gallery_FullWidth_Label" xml:space="preserve">
<value>نوع العرض</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Help" xml:space="preserve">
<value>بعد كم من الوقت يجب أن ننعش المعرض للحصول على محتوى جديد</value>
</data>
<data name="Settings_Gallery_IdleRefreshMins_Label" xml:space="preserve">
<value>Idle Refresh Min</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Help" xml:space="preserve">
<value>العدد الأقصى للأصناف المراد عرضها في الصفحة</value>
</data>
<data name="Settings_Gallery_ItemsPerPage_Label" xml:space="preserve">
<value>البنود الصفحة</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Help" xml:space="preserve">
<value>ما هو أكبر في MB ملف محمول يمكن أن يكون</value>
</data>
<data name="Settings_Gallery_MaxFileSizeMb_Label" xml:space="preserve">
<value>Max File Size (MB)</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Help" xml:space="preserve">
<value>كم هو كبير في إم بي هو المعرض المسموح له أن ينمو</value>
</data>
<data name="Settings_Gallery_MaxSizeMb_Label" xml:space="preserve">
<value>مقاس ماكس غالاري</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Help" xml:space="preserve">
<value>في حالة رفض ازدواجية البنود تلقائياً</value>
</data>
<data name="Settings_Gallery_PreventDuplicates_Label" xml:space="preserve">
<value>منع الازدواجية</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Help" xml:space="preserve">
<value>مرة واحدة مسح ما هو النظام</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultSort_Label" xml:space="preserve">
<value>Default QR Sort</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Help" xml:space="preserve">
<value>مرة واحدة مسح ما التصميم إذا رأى المستخدم</value>
</data>
<data name="Settings_Gallery_QRCodeDefaultView_Label" xml:space="preserve">
<value>Default QR View</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Help" xml:space="preserve">
<value>إذا تم عرض رمز QR</value>
</data>
<data name="Settings_Gallery_QRCodeEnabled_Label" xml:space="preserve">
<value>يُظهرُ الرمز QR</value>
</data>
<data name="Settings_Gallery_Quote_Help" xml:space="preserve">
<value>اقتباس للعرض على قمة المعرض</value>
</data>
<data name="Settings_Gallery_Quote_Label" xml:space="preserve">
<value>Quote</value>
</data>
<data name="Settings_Gallery_RequireReview_Help" xml:space="preserve">
<value>هل يتعين استعراض البنود قبل أن تظهر في الموقع</value>
</data>
<data name="Settings_Gallery_RequireReview_Label" xml:space="preserve">
<value>الاستعراض المطلوب</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Help" xml:space="preserve">
<value>ينبغي تخزين الأصناف المرفوضة لاستخدامها في وقت لاحق</value>
</data>
<data name="Settings_Gallery_RetainRejectedItems_Label" xml:space="preserve">
<value>Retain Rejected items</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Help" xml:space="preserve">
<value>في حالة عرض مكتب الاستعراض</value>
</data>
<data name="Settings_Gallery_ReviewCounter_Label" xml:space="preserve">
<value>الاستعراض</value>
</data>
<data name="Settings_Gallery_SecretKey_Help" xml:space="preserve">
<value>كلمة سر لفتح المعرض</value>
</data>
<data name="Settings_Gallery_SecretKey_Label" xml:space="preserve">
<value>المفتاح السري</value>
</data>
<data name="Settings_Gallery_Upload_Help" xml:space="preserve">
<value>هل يسمح للضيوف بتحميل المواد</value>
</data>
<data name="Settings_Gallery_Upload_Label" xml:space="preserve">
<value>السماح بتحميلات</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Help" xml:space="preserve">
<value>تقييد الحمولات لفترة محددة</value>
</data>
<data name="Settings_Gallery_UploadPeriod_Label" xml:space="preserve">
<value>فترات تحميل</value>
</data>
<data name="Settings_Gotify" xml:space="preserve">
<value>التحصيل</value>
</data>
<data name="Settings_Gotify_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Gotify_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Gotify_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Gotify_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Gotify_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Gotify_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Gotify_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Gotify_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_IdentityCheck" xml:space="preserve">
<value>التحقق من الهوية</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Help" xml:space="preserve">
<value>المستعملون الفوريون لإسمهم</value>
</data>
<data name="Settings_IdentityCheck_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Help" xml:space="preserve">
<value>إذا كان فحص الهوية يُعرض في الصفحة</value>
</data>
<data name="Settings_IdentityCheck_PageLoad_Label" xml:space="preserve">
<value>الصفحة</value>
</data>
<data name="Settings_IdentityCheck_Upload_Help" xml:space="preserve">
<value>إذا طُلب التحقق من الهوية لتحميل وسائط الإعلام</value>
</data>
<data name="Settings_IdentityCheck_Upload_Label" xml:space="preserve">
<value>تحميل</value>
</data>
<data name="Settings_Languages" xml:space="preserve">
<value>اللغات</value>
</data>
<data name="Settings_Languages_Default_Help" xml:space="preserve">
<value>:: لغة الموقع الشبكي الافتراضي إذا لم يُختار أحد</value>
</data>
<data name="Settings_Languages_Default_Label" xml:space="preserve">
<value>اللغة الافتراضية</value>
</data>
<data name="Settings_Languages_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير اللغة</value>
</data>
<data name="Settings_Languages_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy" xml:space="preserve">
<value>Ntfy</value>
</data>
<data name="Settings_Ntfy_Enabled_Help" xml:space="preserve">
<value>هل ينبغي إرسال إنذارات نتيفي</value>
</data>
<data name="Settings_Ntfy_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Ntfy_Endpoint_Help" xml:space="preserve">
<value>عنوان الخادم</value>
</data>
<data name="Settings_Ntfy_Endpoint_Label" xml:space="preserve">
<value>نقطة النهاية</value>
</data>
<data name="Settings_Ntfy_Priority_Help" xml:space="preserve">
<value>قيمة الإنذارات الهامة</value>
</data>
<data name="Settings_Ntfy_Priority_Label" xml:space="preserve">
<value>الأولوية</value>
</data>
<data name="Settings_Ntfy_Token_Help" xml:space="preserve">
<value>المركب المستخدم للتوثيق</value>
</data>
<data name="Settings_Ntfy_Token_Label" xml:space="preserve">
<value>Access Token</value>
</data>
<data name="Settings_Ntfy_Topic_Help" xml:space="preserve">
<value>موضوع إرسال إنذارات</value>
</data>
<data name="Settings_Ntfy_Topic_Label" xml:space="preserve">
<value>الموضوع</value>
</data>
<data name="Settings_Slideshow" xml:space="preserve">
<value>Slideshow</value>
</data>
<data name="Settings_Slideshow_Fade_Help" xml:space="preserve">
<value>كم من الوقت يجب أن يكون الانتقال بين الشرائح</value>
</data>
<data name="Settings_Slideshow_Fade_Label" xml:space="preserve">
<value>Fade</value>
</data>
<data name="Settings_Slideshow_Interval_Help" xml:space="preserve">
<value>كم من الوقت في الثواني يجب أن ينزلق قبل الانتقال إلى الشريحة التالية</value>
</data>
<data name="Settings_Slideshow_Interval_Label" xml:space="preserve">
<value>الفترات الفاصلة</value>
</data>
<data name="Settings_Slideshow_Limit_Help" xml:space="preserve">
<value>الحد الأقصى لعدد الشرائح التي ستدرج</value>
</data>
<data name="Settings_Slideshow_Limit_Label" xml:space="preserve">
<value>Limit</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Help" xml:space="preserve">
<value>هل يجب أن تُرفض شفرة (كيو آر) في النهاية للضيوف</value>
</data>
<data name="Settings_Slideshow_Share_Slide_Label" xml:space="preserve">
<value>Include Share Slide</value>
</data>
<data name="Settings_Smtp" xml:space="preserve">
<value>Email</value>
</data>
<data name="Settings_Smtp_Display_Name_Help" xml:space="preserve">
<value>اسم المرسل الصديق للمستعمل</value>
</data>
<data name="Settings_Smtp_Display_Name_Label" xml:space="preserve">
<value>الاسم</value>
</data>
<data name="Settings_Smtp_Enabled_Help" xml:space="preserve">
<value>ينبغي إرسال تنبيهات بالبريد الإلكتروني</value>
</data>
<data name="Settings_Smtp_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Settings_Smtp_From_Help" xml:space="preserve">
<value>عنوان البريد الإلكتروني أرسل من</value>
</data>
<data name="Settings_Smtp_From_Label" xml:space="preserve">
<value>من</value>
</data>
<data name="Settings_Smtp_Host_Help" xml:space="preserve">
<value>The hostname of the SMTP provider</value>
</data>
<data name="Settings_Smtp_Host_Label" xml:space="preserve">
<value>المضيفة</value>
</data>
<data name="Settings_Smtp_Password_Help" xml:space="preserve">
<value>كلمة السر المستخدمة لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Password_Label" xml:space="preserve">
<value>كلمة السر</value>
</data>
<data name="Settings_Smtp_Port_Help" xml:space="preserve">
<value>The port used by the SMTP provider</value>
</data>
<data name="Settings_Smtp_Port_Label" xml:space="preserve">
<value>الميناء</value>
</data>
<data name="Settings_Smtp_Recipient_Help" xml:space="preserve">
<value>قائمة بريد إلكتروني منفصلة</value>
</data>
<data name="Settings_Smtp_Recipient_Label" xml:space="preserve">
<value>المتلقين</value>
</data>
<data name="Settings_Smtp_Use_SSL_Help" xml:space="preserve">
<value>هل يستخدم مقدم خدمات SMTP SSL</value>
</data>
<data name="Settings_Smtp_Use_SSL_Label" xml:space="preserve">
<value>Use SSL</value>
</data>
<data name="Settings_Smtp_Username_Help" xml:space="preserve">
<value>الاسم المستخدم لتوثيق الطلب</value>
</data>
<data name="Settings_Smtp_Username_Label" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Settings_Themes" xml:space="preserve">
<value>المواضيع</value>
</data>
<data name="Settings_Themes_Default_Help" xml:space="preserve">
<value>الموضوع الرئيسي للموقع إذا لم يُختار أحد</value>
</data>
<data name="Settings_Themes_Default_Label" xml:space="preserve">
<value>التقصير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Help" xml:space="preserve">
<value>السماح للمستعملين بتغيير الموضوع</value>
</data>
<data name="Settings_Themes_Enabled_Label" xml:space="preserve">
<value>Enabled</value>
</data>
<data name="Share_Code" xml:space="preserve">
<value>Share Code</value>
</data>
<data name="Sort" xml:space="preserve">
<value>Sort</value>
</data>
<data name="Space_Percent_Used" xml:space="preserve">
<value>Usage</value>
</data>
<data name="Total" xml:space="preserve">
<value>المجموع</value>
</data>
<data name="Unknown_Review_Action" xml:space="preserve">
<value>إجراء استعراض غير معروف</value>
</data>
<data name="Update" xml:space="preserve">
<value>آخر</value>
</data>
<data name="Upload_Media" xml:space="preserve">
<value>تحميل وسائط الإعلام</value>
</data>
<data name="Uploaded_By" xml:space="preserve">
<value>محمولة</value>
</data>
<data name="Uploader" xml:space="preserve">
<value>الحمولة</value>
</data>
<data name="User_CPassword_Missmatch" xml:space="preserve">
<value>كلمة السر المقدّمة وكلمة سر الكونفريم لا تتطابق</value>
</data>
<data name="User_Name_Already_Exists" xml:space="preserve">
<value>يضاف المستخدم الذي يحمل نفس الاسم موجود بالفعل</value>
</data>
<data name="Username" xml:space="preserve">
<value>المستعمل</value>
</data>
<data name="Users" xml:space="preserve">
<value>المستخدمون</value>
</data>
<data name="Users_List_Failed" xml:space="preserve">
<value>فشل في الحصول على قائمة المستخدمين</value>
</data>
<data name="View" xml:space="preserve">
<value>View</value>
</data>
<data name="Visit" xml:space="preserve">
<value>زيارة</value>
</data>
<data name="Wipe" xml:space="preserve">
<value>Wipe</value>
</data>
<data name="Wipe_All_Confirmation" xml:space="preserve">
<value>هل أنت متأكد من أنك تريد مسح كل المجرات؟?</value>
</data>
<data name="Wipe_Confirmation" xml:space="preserve">
<value>هل أنت متأكد أنك تريد مسح هذا المعرض؟?</value>
</data>
</root>

Some files were not shown because too many files have changed in this diff Show More