thingsboard-aplcache
Changes
dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java 73(+46 -27)
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java 175(+168 -7)
ui/package.json 6(+4 -2)
ui/package-lock.json 143(+143 -0)
ui/src/app/locale/locale.constant-fa_IR.json 1582(+1582 -0)
ui/webpack.config.dev.js 28(+24 -4)
ui/webpack.config.prod.js 30(+27 -3)
Details
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java
index a6d3ea6..fe10286 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java
@@ -49,25 +49,53 @@ import static org.thingsboard.server.dao.model.ModelConstants.TS_COLUMN;
@IdClass(TsKvCompositeKey.class)
public final class TsKvEntity implements ToData<TsKvEntry> {
- public TsKvEntity() {
- }
+ private static final String SUM = "SUM";
+ private static final String AVG = "AVG";
+ private static final String MIN = "MIN";
+ private static final String MAX = "MAX";
- public TsKvEntity(Double avgLongValue, Double avgDoubleValue) {
- if(avgLongValue != null) {
- this.longValue = avgLongValue.longValue();
- }
- this.doubleValue = avgDoubleValue;
+ public TsKvEntity() {
}
- public TsKvEntity(Long sumLongValue, Double sumDoubleValue) {
- this.longValue = sumLongValue;
- this.doubleValue = sumDoubleValue;
+ public TsKvEntity(String strValue) {
+ this.strValue = strValue;
}
- public TsKvEntity(String strValue, Long longValue, Double doubleValue) {
- this.strValue = strValue;
- this.longValue = longValue;
- this.doubleValue = doubleValue;
+ public TsKvEntity(Long longValue, Double doubleValue, Long longCountValue, Long doubleCountValue, String aggType) {
+ switch (aggType) {
+ case AVG:
+ double sum = 0.0;
+ if (longValue != null) {
+ sum += longValue;
+ }
+ if (doubleValue != null) {
+ sum += doubleValue;
+ }
+ long totalCount = longCountValue + doubleCountValue;
+ if (totalCount > 0) {
+ this.doubleValue = sum / (longCountValue + doubleCountValue);
+ } else {
+ this.doubleValue = 0.0;
+ }
+ break;
+ case SUM:
+ if (doubleCountValue > 0) {
+ this.doubleValue = doubleValue + (longValue != null ? longValue.doubleValue() : 0.0);
+ } else {
+ this.longValue = longValue;
+ }
+ break;
+ case MIN:
+ case MAX:
+ if (longCountValue > 0 && doubleCountValue > 0) {
+ this.doubleValue = MAX.equals(aggType) ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue());
+ } else if (doubleCountValue > 0) {
+ this.doubleValue = doubleValue;
+ } else if (longCountValue > 0) {
+ this.longValue = longValue;
+ }
+ break;
+ }
}
public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) {
@@ -75,10 +103,8 @@ public final class TsKvEntity implements ToData<TsKvEntry> {
this.longValue = booleanValueCount;
} else if (strValueCount != 0) {
this.longValue = strValueCount;
- } else if (longValueCount != 0) {
- this.longValue = longValueCount;
- } else if (doubleValueCount != 0) {
- this.longValue = doubleValueCount;
+ } else {
+ this.longValue = longValueCount + doubleValueCount;
}
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
index 3120148..a04944e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
@@ -161,52 +161,62 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
}
private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, String key, long startTs, long endTs, long ts, Aggregation aggregation) {
- CompletableFuture<TsKvEntity> entity;
+ List<CompletableFuture<TsKvEntity>> entitiesFutures = new ArrayList<>();
String entityIdStr = fromTimeUUID(entityId.getId());
switch (aggregation) {
case AVG:
- entity = tsKvRepository.findAvg(
+ entitiesFutures.add(tsKvRepository.findAvg(
entityIdStr,
entityId.getEntityType(),
key,
startTs,
- endTs);
+ endTs));
break;
case MAX:
- entity = tsKvRepository.findMax(
+ entitiesFutures.add(tsKvRepository.findStringMax(
entityIdStr,
entityId.getEntityType(),
key,
startTs,
- endTs);
+ endTs));
+ entitiesFutures.add(tsKvRepository.findNumericMax(
+ entityIdStr,
+ entityId.getEntityType(),
+ key,
+ startTs,
+ endTs));
break;
case MIN:
- entity = tsKvRepository.findMin(
+ entitiesFutures.add(tsKvRepository.findStringMin(
entityIdStr,
entityId.getEntityType(),
key,
startTs,
- endTs);
-
+ endTs));
+ entitiesFutures.add(tsKvRepository.findNumericMin(
+ entityIdStr,
+ entityId.getEntityType(),
+ key,
+ startTs,
+ endTs));
break;
case SUM:
- entity = tsKvRepository.findSum(
+ entitiesFutures.add(tsKvRepository.findSum(
entityIdStr,
entityId.getEntityType(),
key,
startTs,
- endTs);
-
+ endTs));
break;
case COUNT:
- entity = tsKvRepository.findCount(
+ entitiesFutures.add(tsKvRepository.findCount(
entityIdStr,
entityId.getEntityType(),
key,
startTs,
- endTs);
+ endTs));
break;
default:
@@ -214,11 +224,27 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
}
SettableFuture<TsKvEntity> listenableFuture = SettableFuture.create();
- entity.whenComplete((tsKvEntity, throwable) -> {
+
+
+ CompletableFuture<List<TsKvEntity>> entities =
+ CompletableFuture.allOf(entitiesFutures.toArray(new CompletableFuture[entitiesFutures.size()]))
+ .thenApply(v -> entitiesFutures.stream()
+ .map(CompletableFuture::join)
+ .collect(Collectors.toList()));
+
+
+ entities.whenComplete((tsKvEntities, throwable) -> {
if (throwable != null) {
listenableFuture.setException(throwable);
} else {
- listenableFuture.set(tsKvEntity);
+ TsKvEntity result = null;
+ for (TsKvEntity entity : tsKvEntities) {
+ if (entity.isNotEmpty()) {
+ result = entity;
+ break;
+ }
+ }
+ listenableFuture.set(result);
}
});
return Futures.transform(listenableFuture, new Function<TsKvEntity, Optional<TsKvEntry>>() {
diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
index 296d173..79aa71b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
@@ -35,7 +35,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
"AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
- "AND tskv.ts > :startTs AND tskv.ts < :endTs")
+ "AND tskv.ts > :startTs AND tskv.ts <= :endTs")
List<TsKvEntity> findAllWithLimit(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String key,
@@ -55,29 +55,63 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Param("endTs") long endTs);
@Async
- @Query("SELECT new TsKvEntity(MAX(tskv.strValue), MAX(tskv.longValue), MAX(tskv.doubleValue)) FROM TsKvEntity tskv " +
+ @Query("SELECT new TsKvEntity(MAX(tskv.strValue)) FROM TsKvEntity tskv " +
+ "WHERE tskv.strValue IS NOT NULL " +
+ "AND tskv.entityId = :entityId AND tskv.entityType = :entityType " +
+ "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
+ CompletableFuture<TsKvEntity> findStringMax(@Param("entityId") String entityId,
+ @Param("entityType") EntityType entityType,
+ @Param("entityKey") String entityKey,
+ @Param("startTs") long startTs,
+ @Param("endTs") long endTs);
+
+ @Async
+ @Query("SELECT new TsKvEntity(MAX(COALESCE(tskv.longValue, -9223372036854775807)), " +
+ "MAX(COALESCE(tskv.doubleValue, -1.79769E+308)), " +
+ "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
+ "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
+ "'MAX') FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
- "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
- CompletableFuture<TsKvEntity> findMax(@Param("entityId") String entityId,
+ "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
+ CompletableFuture<TsKvEntity> findNumericMax(@Param("entityId") String entityId,
+ @Param("entityType") EntityType entityType,
+ @Param("entityKey") String entityKey,
+ @Param("startTs") long startTs,
+ @Param("endTs") long endTs);
+
+
+ @Async
+ @Query("SELECT new TsKvEntity(MIN(tskv.strValue)) FROM TsKvEntity tskv " +
+ "WHERE tskv.strValue IS NOT NULL " +
+ "AND tskv.entityId = :entityId AND tskv.entityType = :entityType " +
+ "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
+ CompletableFuture<TsKvEntity> findStringMin(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Async
- @Query("SELECT new TsKvEntity(MIN(tskv.strValue), MIN(tskv.longValue), MIN(tskv.doubleValue)) FROM TsKvEntity tskv " +
+ @Query("SELECT new TsKvEntity(MIN(COALESCE(tskv.longValue, 9223372036854775807)), " +
+ "MIN(COALESCE(tskv.doubleValue, 1.79769E+308)), " +
+ "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
+ "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
+ "'MIN') FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
- "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
- CompletableFuture<TsKvEntity> findMin(@Param("entityId") String entityId,
+ "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
+ CompletableFuture<TsKvEntity> findNumericMin(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Async
- @Query("SELECT new TsKvEntity(COUNT(tskv.booleanValue), COUNT(tskv.strValue), COUNT(tskv.longValue), COUNT(tskv.doubleValue)) FROM TsKvEntity tskv " +
+ @Query("SELECT new TsKvEntity(SUM(CASE WHEN tskv.booleanValue IS NULL THEN 0 ELSE 1 END), " +
+ "SUM(CASE WHEN tskv.strValue IS NULL THEN 0 ELSE 1 END), " +
+ "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
+ "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
- "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
+ "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
CompletableFuture<TsKvEntity> findCount(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@@ -85,23 +119,31 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Param("endTs") long endTs);
@Async
- @Query("SELECT new TsKvEntity(AVG(tskv.longValue), AVG(tskv.doubleValue)) FROM TsKvEntity tskv " +
+ @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " +
+ "SUM(COALESCE(tskv.doubleValue, 0.0)), " +
+ "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
+ "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
+ "'AVG') FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
- "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
+ "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
CompletableFuture<TsKvEntity> findAvg(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
-
@Async
- @Query("SELECT new TsKvEntity(SUM(tskv.longValue), SUM(tskv.doubleValue)) FROM TsKvEntity tskv " +
+ @Query("SELECT new TsKvEntity(SUM(COALESCE(tskv.longValue, 0)), " +
+ "SUM(COALESCE(tskv.doubleValue, 0.0)), " +
+ "SUM(CASE WHEN tskv.longValue IS NULL THEN 0 ELSE 1 END), " +
+ "SUM(CASE WHEN tskv.doubleValue IS NULL THEN 0 ELSE 1 END), " +
+ "'SUM') FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
- "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
+ "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts <= :endTs")
CompletableFuture<TsKvEntity> findSum(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java
index ac5ee64..ea1e8f1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java
@@ -79,7 +79,7 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
}
private void processResultSetRow(Row row, AggregationResult aggResult) {
- long curCount;
+ long curCount = 0L;
Long curLValue = null;
Double curDValue = null;
@@ -91,14 +91,18 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
long boolCount = row.getLong(BOOL_CNT_POS);
long strCount = row.getLong(STR_CNT_POS);
- if (longCount > 0) {
- aggResult.dataType = DataType.LONG;
- curCount = longCount;
- curLValue = getLongValue(row);
- } else if (doubleCount > 0) {
- aggResult.dataType = DataType.DOUBLE;
- curCount = doubleCount;
- curDValue = getDoubleValue(row);
+ if (longCount > 0 || doubleCount > 0) {
+ if (longCount > 0) {
+ aggResult.dataType = DataType.LONG;
+ curCount += longCount;
+ curLValue = getLongValue(row);
+ }
+ if (doubleCount > 0) {
+ aggResult.hasDouble = true;
+ aggResult.dataType = DataType.DOUBLE;
+ curCount += doubleCount;
+ curDValue = getDoubleValue(row);
+ }
} else if (boolCount > 0) {
aggResult.dataType = DataType.BOOLEAN;
curCount = boolCount;
@@ -126,16 +130,20 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
aggResult.count += curCount;
if (curDValue != null) {
aggResult.dValue = aggResult.dValue == null ? curDValue : aggResult.dValue + curDValue;
- } else if (curLValue != null) {
+ }
+ if (curLValue != null) {
aggResult.lValue = aggResult.lValue == null ? curLValue : aggResult.lValue + curLValue;
}
}
private void processMinAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) {
- if (curDValue != null) {
- aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue);
- } else if (curLValue != null) {
- aggResult.lValue = aggResult.lValue == null ? curLValue : Math.min(aggResult.lValue, curLValue);
+ if (curDValue != null || curLValue != null) {
+ if (curDValue != null) {
+ aggResult.dValue = aggResult.dValue == null ? curDValue : Math.min(aggResult.dValue, curDValue);
+ }
+ if (curLValue != null) {
+ aggResult.lValue = aggResult.lValue == null ? curLValue : Math.min(aggResult.lValue, curLValue);
+ }
} else if (curBValue != null) {
aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue && curBValue;
} else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) < 0)) {
@@ -144,10 +152,13 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
}
private void processMaxAggregation(AggregationResult aggResult, Long curLValue, Double curDValue, Boolean curBValue, String curSValue) {
- if (curDValue != null) {
- aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue);
- } else if (curLValue != null) {
- aggResult.lValue = aggResult.lValue == null ? curLValue : Math.max(aggResult.lValue, curLValue);
+ if (curDValue != null || curLValue != null) {
+ if (curDValue != null) {
+ aggResult.dValue = aggResult.dValue == null ? curDValue : Math.max(aggResult.dValue, curDValue);
+ }
+ if (curLValue != null) {
+ aggResult.lValue = aggResult.lValue == null ? curLValue : Math.max(aggResult.lValue, curLValue);
+ }
} else if (curBValue != null) {
aggResult.bValue = aggResult.bValue == null ? curBValue : aggResult.bValue || curBValue;
} else if (curSValue != null && (aggResult.sValue == null || curSValue.compareTo(aggResult.sValue) > 0)) {
@@ -211,20 +222,27 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
private Optional<TsKvEntry> processAvgOrSumResult(AggregationResult aggResult) {
if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) {
return Optional.empty();
- } else if (aggResult.dataType == DataType.DOUBLE) {
- return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? aggResult.dValue : (aggResult.dValue / aggResult.count))));
- } else if (aggResult.dataType == DataType.LONG) {
- return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count))));
+ } else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) {
+ if(aggregation == Aggregation.AVG || aggResult.hasDouble) {
+ double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L);
+ return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count))));
+ } else {
+ return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count))));
+ }
}
return Optional.empty();
}
private Optional<TsKvEntry> processMinOrMaxResult(AggregationResult aggResult) {
- if (aggResult.dataType == DataType.DOUBLE) {
- return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggResult.dValue)));
- } else if (aggResult.dataType == DataType.LONG) {
- return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue)));
- } else if (aggResult.dataType == DataType.STRING) {
+ if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) {
+ if(aggResult.hasDouble) {
+ double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE);
+ double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE);
+ return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL))));
+ } else {
+ return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue)));
+ }
+ } else if (aggResult.dataType == DataType.STRING) {
return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue)));
} else {
return Optional.of(new BasicTsKvEntry(ts, new BooleanDataEntry(key, aggResult.bValue)));
@@ -238,5 +256,6 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
Double dValue = null;
Long lValue = null;
long count = 0;
+ boolean hasDouble = false;
}
}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java
index 55c2f70..ddea70a 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java
@@ -25,9 +25,7 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClassnameFilters({
- "org.thingsboard.server.dao.service.*ServiceNoSqlTest",
- "org.thingsboard.server.dao.service.queue.cassandra.*.*.*Test",
- "org.thingsboard.server.dao.service.queue.cassandra.*Test"
+ "org.thingsboard.server.dao.service.*ServiceNoSqlTest"
})
public class NoSqlDaoServiceTestSuite {
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
index b409dea..c2af9d6 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
@@ -17,10 +17,7 @@ package org.thingsboard.server.dao.service.timeseries;
import com.datastax.driver.core.utils.UUIDs;
import lombok.extern.slf4j.Slf4j;
-import org.junit.After;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Test;
+import org.junit.*;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.DeviceId;
@@ -221,13 +218,13 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
60000, 20000, 3, Aggregation.AVG))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
- assertEquals(java.util.Optional.of(150L), list.get(0).getLongValue());
+ assertEquals(java.util.Optional.of(150.0), list.get(0).getDoubleValue());
assertEquals(30000, list.get(1).getTs());
- assertEquals(java.util.Optional.of(350L), list.get(1).getLongValue());
+ assertEquals(java.util.Optional.of(350.0), list.get(1).getDoubleValue());
assertEquals(50000, list.get(2).getTs());
- assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
+ assertEquals(java.util.Optional.of(550.0), list.get(2).getDoubleValue());
list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.SUM))).get();
@@ -280,6 +277,157 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue());
+
+
+ entries.add(save(deviceId, 65000, "A1"));
+ entries.add(save(deviceId, 75000, "A2"));
+ entries.add(save(deviceId, 85000, "B1"));
+ entries.add(save(deviceId, 95000, "B2"));
+ entries.add(save(deviceId, 105000, "C1"));
+ entries.add(save(deviceId, 115000, "C2"));
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000,
+ 120000, 20000, 3, Aggregation.NONE))).get();
+ assertEquals(3, list.size());
+ assertEquals(115000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of("C2"), list.get(0).getStrValue());
+
+ assertEquals(105000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of("C1"), list.get(1).getStrValue());
+
+ assertEquals(95000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of("B2"), list.get(2).getStrValue());
+
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000,
+ 120000, 20000, 3, Aggregation.MIN))).get();
+
+ assertEquals(3, list.size());
+ assertEquals(70000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of("A1"), list.get(0).getStrValue());
+
+ assertEquals(90000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of("B1"), list.get(1).getStrValue());
+
+ assertEquals(110000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of("C1"), list.get(2).getStrValue());
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000,
+ 120000, 20000, 3, Aggregation.MAX))).get();
+
+ assertEquals(3, list.size());
+ assertEquals(70000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of("A2"), list.get(0).getStrValue());
+
+ assertEquals(90000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of("B2"), list.get(1).getStrValue());
+
+ assertEquals(110000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of("C2"), list.get(2).getStrValue());
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 60000,
+ 120000, 20000, 3, Aggregation.COUNT))).get();
+
+ assertEquals(3, list.size());
+ assertEquals(70000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of(2L), list.get(0).getLongValue());
+
+ assertEquals(90000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of(2L), list.get(1).getLongValue());
+
+ assertEquals(110000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue());
+ }
+
+ @Test
+ public void testFindDeviceLongAndDoubleTsData() throws Exception {
+ DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+ List<TsKvEntry> entries = new ArrayList<>();
+
+ entries.add(save(deviceId, 5000, 100));
+ entries.add(save(deviceId, 15000, 200.0));
+
+ entries.add(save(deviceId, 25000, 300));
+ entries.add(save(deviceId, 35000, 400.0));
+
+ entries.add(save(deviceId, 45000, 500));
+ entries.add(save(deviceId, 55000, 600.0));
+
+ List<TsKvEntry> list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
+ 60000, 20000, 3, Aggregation.NONE))).get();
+ assertEquals(3, list.size());
+ assertEquals(55000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of(600.0), list.get(0).getDoubleValue());
+
+ assertEquals(45000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of(500L), list.get(1).getLongValue());
+
+ assertEquals(35000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of(400.0), list.get(2).getDoubleValue());
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
+ 60000, 20000, 3, Aggregation.AVG))).get();
+ assertEquals(3, list.size());
+ assertEquals(10000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of(150.0), list.get(0).getDoubleValue());
+
+ assertEquals(30000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of(350.0), list.get(1).getDoubleValue());
+
+ assertEquals(50000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of(550.0), list.get(2).getDoubleValue());
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
+ 60000, 20000, 3, Aggregation.SUM))).get();
+
+ assertEquals(3, list.size());
+ assertEquals(10000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of(300.0), list.get(0).getDoubleValue());
+
+ assertEquals(30000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of(700.0), list.get(1).getDoubleValue());
+
+ assertEquals(50000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of(1100.0), list.get(2).getDoubleValue());
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
+ 60000, 20000, 3, Aggregation.MIN))).get();
+
+ assertEquals(3, list.size());
+ assertEquals(10000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of(100.0), list.get(0).getDoubleValue());
+
+ assertEquals(30000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of(300.0), list.get(1).getDoubleValue());
+
+ assertEquals(50000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of(500.0), list.get(2).getDoubleValue());
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
+ 60000, 20000, 3, Aggregation.MAX))).get();
+
+ assertEquals(3, list.size());
+ assertEquals(10000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of(200.0), list.get(0).getDoubleValue());
+
+ assertEquals(30000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of(400.0), list.get(1).getDoubleValue());
+
+ assertEquals(50000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of(600.0), list.get(2).getDoubleValue());
+
+ list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
+ 60000, 20000, 3, Aggregation.COUNT))).get();
+
+ assertEquals(3, list.size());
+ assertEquals(10000, list.get(0).getTs());
+ assertEquals(java.util.Optional.of(2L), list.get(0).getLongValue());
+
+ assertEquals(30000, list.get(1).getTs());
+ assertEquals(java.util.Optional.of(2L), list.get(1).getLongValue());
+
+ assertEquals(50000, list.get(2).getTs());
+ assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue());
}
private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception {
@@ -288,6 +436,19 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
return entry;
}
+ private TsKvEntry save(DeviceId deviceId, long ts, double value) throws Exception {
+ TsKvEntry entry = new BasicTsKvEntry(ts, new DoubleDataEntry(LONG_KEY, value));
+ tsService.save(tenantId, deviceId, entry).get();
+ return entry;
+ }
+
+ private TsKvEntry save(DeviceId deviceId, long ts, String value) throws Exception {
+ TsKvEntry entry = new BasicTsKvEntry(ts, new StringDataEntry(LONG_KEY, value));
+ tsService.save(tenantId, deviceId, entry).get();
+ return entry;
+ }
+
+
private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException {
tsService.save(tenantId, deviceId, toTsEntry(ts, stringKvEntry)).get();
tsService.save(tenantId, deviceId, toTsEntry(ts, longKvEntry)).get();
ui/package.json 6(+4 -2)
diff --git a/ui/package.json b/ui/package.json
index 20258ad..7345ae6 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -11,7 +11,7 @@
],
"scripts": {
"start": "babel-node --max_old_space_size=4096 server.js",
- "build": "cross-env NODE_ENV=production webpack -p"
+ "build": "cross-env NODE_ENV=production webpack"
},
"dependencies": {
"@flowjs/ng-flow": "^2.7.1",
@@ -136,7 +136,9 @@
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.15.1",
"webpack-hot-middleware": "^2.12.2",
- "webpack-material-design-icons": "^0.1.0"
+ "webpack-material-design-icons": "^0.1.0",
+ "uglifyjs-webpack-plugin": "^1.3.0",
+ "happypack": "^5.0.1"
},
"engine": "node >= 5.9.0",
"nyc": {
ui/package-lock.json 143(+143 -0)
diff --git a/ui/package-lock.json b/ui/package-lock.json
index 7c0604d..30fd603 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -6264,6 +6264,37 @@
"glogg": "^1.0.0"
}
},
+ "happypack": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/happypack/-/happypack-5.0.1.tgz",
+ "integrity": "sha512-AzXVxLzX0mtv0T40Kic72rfcGK4Y2b/cDdtcyw+e+V/13ozl7x0+EZ4hvrL1rJ8MoefR9+FfUJQsK2irH0GWOw==",
+ "dev": true,
+ "requires": {
+ "async": "1.5.0",
+ "json-stringify-safe": "5.0.1",
+ "loader-utils": "1.1.0",
+ "serialize-error": "^2.1.0"
+ },
+ "dependencies": {
+ "async": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-1.5.0.tgz",
+ "integrity": "sha1-J5ZkJyNXOFlWVjP8YnRES+4vjOM=",
+ "dev": true
+ },
+ "loader-utils": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz",
+ "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=",
+ "dev": true,
+ "requires": {
+ "big.js": "^3.1.3",
+ "emojis-list": "^2.0.0",
+ "json5": "^0.5.0"
+ }
+ }
+ }
+ },
"har-schema": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz",
@@ -12928,6 +12959,12 @@
}
}
},
+ "serialize-error": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
+ "integrity": "sha1-ULZ51WNc34Rme9yOWa9OW4HV9go=",
+ "dev": true
+ },
"serialize-javascript": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz",
@@ -15377,6 +15414,103 @@
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=",
"dev": true
},
+ "uglifyjs-webpack-plugin": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz",
+ "integrity": "sha512-ovHIch0AMlxjD/97j9AYovZxG5wnHOPkL7T1GKochBADp/Zwc44pEWNqpKl1Loupp1WhFg7SlYmHZRUfdAacgw==",
+ "dev": true,
+ "requires": {
+ "cacache": "^10.0.4",
+ "find-cache-dir": "^1.0.0",
+ "schema-utils": "^0.4.5",
+ "serialize-javascript": "^1.4.0",
+ "source-map": "^0.6.1",
+ "uglify-es": "^3.3.4",
+ "webpack-sources": "^1.1.0",
+ "worker-farm": "^1.5.2"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz",
+ "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==",
+ "dev": true,
+ "requires": {
+ "fast-deep-equal": "^2.0.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ajv-keywords": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.3.0.tgz",
+ "integrity": "sha512-CMzN9S62ZOO4sA/mJZIO4S++ZM7KFWzH3PPWkveLhy4OZ9i1/VatgwWMD46w/XbGCBy7Ye0gCk+Za6mmyfKK7g==",
+ "dev": true
+ },
+ "commander": {
+ "version": "2.13.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz",
+ "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==",
+ "dev": true
+ },
+ "find-cache-dir": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz",
+ "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=",
+ "dev": true,
+ "requires": {
+ "commondir": "^1.0.1",
+ "make-dir": "^1.0.0",
+ "pkg-dir": "^2.0.0"
+ }
+ },
+ "find-up": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
+ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+ "dev": true,
+ "requires": {
+ "locate-path": "^2.0.0"
+ }
+ },
+ "pkg-dir": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz",
+ "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=",
+ "dev": true,
+ "requires": {
+ "find-up": "^2.1.0"
+ }
+ },
+ "schema-utils": {
+ "version": "0.4.7",
+ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.4.7.tgz",
+ "integrity": "sha512-v/iwU6wvwGK8HbU9yi3/nhGzP0yGSuhQMzL6ySiec1FSrZZDkhm4noOSWzrNFo/jEc+SJY6jRTwuwbSXJPDUnQ==",
+ "dev": true,
+ "requires": {
+ "ajv": "^6.1.0",
+ "ajv-keywords": "^3.1.0"
+ }
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true
+ },
+ "uglify-es": {
+ "version": "3.3.9",
+ "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz",
+ "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==",
+ "dev": true,
+ "requires": {
+ "commander": "~2.13.0",
+ "source-map": "~0.6.1"
+ }
+ }
+ }
+ },
"ultron": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.0.2.tgz",
@@ -16256,6 +16390,15 @@
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
},
+ "worker-farm": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.6.0.tgz",
+ "integrity": "sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ==",
+ "dev": true,
+ "requires": {
+ "errno": "~0.1.7"
+ }
+ },
"wrap-ansi": {
"version": "2.1.0",
"resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
diff --git a/ui/src/app/locale/locale.constant-de_DE.json b/ui/src/app/locale/locale.constant-de_DE.json
index b777b18..dd462a3 100644
--- a/ui/src/app/locale/locale.constant-de_DE.json
+++ b/ui/src/app/locale/locale.constant-de_DE.json
@@ -1575,7 +1575,8 @@
"ru_RU": "Russisch",
"es_ES": "Spanisch",
"ja_JA": "Japanisch",
- "tr_TR": "Türkisch"
+ "tr_TR": "Türkisch",
+ "fa_IR": "Persisch"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 4f12ee0..eba880d 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -1575,7 +1575,8 @@
"ru_RU": "Russian",
"es_ES": "Spanish",
"ja_JA": "Japanese",
- "tr_TR": "Turkish"
+ "tr_TR": "Turkish",
+ "fa_IR": "Persian"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-es_ES.json b/ui/src/app/locale/locale.constant-es_ES.json
index 8190157..afb96ce 100644
--- a/ui/src/app/locale/locale.constant-es_ES.json
+++ b/ui/src/app/locale/locale.constant-es_ES.json
@@ -1575,7 +1575,8 @@
"ru_RU": "Ruso",
"es_ES": "Español",
"ja_JA": "Japonés",
- "tr_TR": "Turco"
+ "tr_TR": "Turco",
+ "fa_IR": "Persa"
}
}
}
\ No newline at end of file
ui/src/app/locale/locale.constant-fa_IR.json 1582(+1582 -0)
diff --git a/ui/src/app/locale/locale.constant-fa_IR.json b/ui/src/app/locale/locale.constant-fa_IR.json
new file mode 100644
index 0000000..de9b2a4
--- /dev/null
+++ b/ui/src/app/locale/locale.constant-fa_IR.json
@@ -0,0 +1,1582 @@
+{
+ "access": {
+ "unauthorized": "غير مجاز",
+ "unauthorized-access": "دسترسي غير مجاز",
+ "unauthorized-access-text": "!شما بايد وارد شويد تا به اين منبع دسترسي پيدا کنيد",
+ "access-forbidden": "دسترسي ممنوع",
+ "access-forbidden-text": "!اگر هنوز تمايل داريد به اينجا دسترسي پيدا کنيد، تلاش کنيد با نام کاربري ديگري وارد شويد<br/>.شما حق دسترسي به اينجا را نداريد",
+ "refresh-token-expired": "اين بخش، منقضي شده است",
+ "refresh-token-failed": "بازيابي اين بخش ممکن نيست"
+ },
+ "action": {
+ "activate": "فعال سازي",
+ "suspend": "معلّق",
+ "save": "ذخيره سازي",
+ "saveAs": "ذخيره سازي در",
+ "cancel": "لغو",
+ "ok": "قبول",
+ "delete": "حذف",
+ "add": "اضافه",
+ "yes": "بله",
+ "no": "خير",
+ "update": "به روز کردن",
+ "remove": "حذف",
+ "search": "جستجو",
+ "clear-search": "پاک کردن جستجو",
+ "assign": "تخصيص",
+ "unassign": "لغو تخصيص",
+ "share": "به اشتراک گذاري",
+ "make-private": "شخصي سازي",
+ "apply": "اعمال",
+ "apply-changes": "اعمال تغييرات",
+ "edit-mode": "حالت ويرايش",
+ "enter-edit-mode": "ورود به حالت ويرايش",
+ "decline-changes": "عدم پذيرش تغييرات",
+ "close": "بستن",
+ "back": "بازگشت",
+ "run": "اجرا",
+ "sign-in": "!ورود",
+ "edit": "ويرايش",
+ "view": "نمايش",
+ "create": "ايجاد",
+ "drag": "کشيدن",
+ "refresh": "بازيابي",
+ "undo": "برگرداندن آخرين عمل",
+ "copy": "رونوشت",
+ "paste": "الصاق رونوشت",
+ "copy-reference": "رونوشت مرجع",
+ "paste-reference": "رونوشت مرجع",
+ "import": "وارد کردن",
+ "export": "صدور",
+ "share-via": "{{provider}} اشتراک گذاري از طريق"
+ },
+ "aggregation": {
+ "aggregation": "تجميع",
+ "function": "تابع تجميع داده ها",
+ "limit": "بيشترين مقادير",
+ "group-interval": "فاصله گروه بندي",
+ "min": "کمترين",
+ "max": "بيشترين",
+ "avg": "ميانگين",
+ "sum": "جمع",
+ "count": "شمارش",
+ "none": "هيچکدام"
+ },
+ "admin": {
+ "general": "عمومي",
+ "general-settings": "تنظيمات عمومي",
+ "outgoing-mail": "پيام خروجي",
+ "outgoing-mail-settings": "تنظيمات پيام خروجي",
+ "system-settings": "تنظيمات سيستم",
+ "test-mail-sent": "!ارسال پيام آزمايشي موفقيت آميز بود",
+ "base-url": "مبنا URL",
+ "base-url-required": ".مبنا مورد نياز است URL",
+ "mail-from": "... پيام از",
+ "mail-from-required": ".پيام از ... مورد نياز است",
+ "smtp-protocol": "SMTP قرارداد",
+ "smtp-host": "SMTP ميزبان",
+ "smtp-host-required": ".مورد نياز است SMTP ميزبان",
+ "smtp-port": "SMTP درگاه",
+ "smtp-port-required": ".فراهم کنيد SMTP شما بايد يک درگاه",
+ "smtp-port-invalid": ".معتبر باشد SMTP به نظر نمي آيد يک درگاه",
+ "timeout-msec": "مهلت (msec)",
+ "timeout-required": ".مهلت مورد نياز است",
+ "timeout-invalid": ".مهلت، به نظر نمي آيد معتبر باشد",
+ "enable-tls": "TLS فعال سازي",
+ "send-test-mail": "ارسال پيام آزمايشي"
+ },
+ "alarm": {
+ "alarm": "هشدار",
+ "alarms": "هشدارها",
+ "select-alarm": "انتخاب هشدار",
+ "no-alarms-matching": ".يافت نشد '{{entity}}' هيچ هشداري مطابق",
+ "alarm-required": "هشدار مورد نياز است",
+ "alarm-status": "وضعيت هشدار",
+ "search-status": {
+ "ANY": "هر",
+ "ACTIVE": "فعال",
+ "CLEARED": "پاک شده",
+ "ACK": "تصديق شده",
+ "UNACK": "تصديق نشده"
+ },
+ "display-status": {
+ "ACTIVE_UNACK": "تصديق نشده فعال",
+ "ACTIVE_ACK": "تصديق شده فعال",
+ "CLEARED_UNACK": "تصديق نشده پاک شده",
+ "CLEARED_ACK": "تصديق شده پاک شده"
+ },
+ "no-alarms-prompt": "هيچ هشداري يافت نشد",
+ "created-time": "زمان ايجاد",
+ "type": "نوع",
+ "severity": "شدت",
+ "originator": "مبدأ",
+ "originator-type": "نوع مبدأ",
+ "details": "جزئيات",
+ "status": "وضعيت",
+ "alarm-details": "جزئيات هشدار",
+ "start-time": "زمان شروع",
+ "end-time": "زمان پايان",
+ "ack-time": "زمان تصديق",
+ "clear-time": "زمان پاک شدن",
+ "severity-critical": "بحراني",
+ "severity-major": "مهم",
+ "severity-minor": "جزئي",
+ "severity-warning": "اخطار",
+ "severity-indeterminate": "نامشخص",
+ "acknowledge": "تصديق",
+ "clear": "پاک کردن",
+ "search": "جستجوي هشدارها",
+ "selected-alarms": "اننخاب شده { count, plural, 1 {1 هشدار} other {# هشدارها} }",
+ "no-data": "هيچ داده اي براي نمايش نيست",
+ "polling-interval": "هشدار دهنده فاصله نمونه برداري (sec)",
+ "polling-interval-required": ".هشدار دهنده فاصله نمونه برداري مورد نياز است",
+ "min-polling-interval-message": ".حداقل فاصله مجاز نمونه برداري، 1 ثانيه است",
+ "aknowledge-alarms-title": "{ count, plural, 1 {1 هشدار} other {# هشدارها} } تصديق",
+ "aknowledge-alarms-text": "اطمينان داريد؟ { count, plural, 1 {1 هشدار} other {# هشدارها} } آيا شما از تصديق",
+ "aknowledge-alarm-title": "تصديق هشدار",
+ "aknowledge-alarm-text": "آيا شما از تصديق هشدار اطمينان داريد؟",
+ "clear-alarms-title": "{ count, plural, 1 {1 هشدار} other {# هشدارها} } پاک کردن",
+ "clear-alarms-text": "اطمينان داريد؟ { count, plural, 1 {1 هشدار} other {# هشدارها} } آيا شما از پاک کردن",
+ "clear-alarm-title": "پاک کردن هشدار",
+ "clear-alarm-text": "آيا شما از پاک کردن هشدار اطمينان داريد؟",
+ "alarm-status-filter": "فيلتر وضعيت هشدار"
+ },
+ "alias": {
+ "add": "افزودن نام مستعار",
+ "edit": "ويرايش نام مستعار",
+ "name": "نام مستعار",
+ "name-required": "نام مستعار مورد نياز است",
+ "duplicate-alias": ".در حال حاضر نام مستعار مشابهي وجود دارد",
+ "filter-type-single-entity": "موجودي تکي",
+ "filter-type-entity-list": "ليست موجودي",
+ "filter-type-entity-name": "نام موجودي",
+ "filter-type-state-entity": "موجودي از وضعيت داشبورد",
+ "filter-type-state-entity-description": "پارامترهاي موجودي گرفته شده از وضعيت داشبورد",
+ "filter-type-asset-type": "نوع دارايي",
+ "filter-type-asset-type-description": "'{{assetType}}' دارايي هاي نوع",
+ "filter-type-asset-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{assetType}}' دارايي هاي نوع",
+ "filter-type-device-type": "نوع دستگاه",
+ "filter-type-device-type-description": "'{{deviceType}}' دستگاه هاي نوع",
+ "filter-type-device-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{deviceType}}' دستگاه هاي نوع",
+ "filter-type-entity-view-type": "نوع نمايش موجودي",
+ "filter-type-entity-view-type-description": "'{{entityView}}' نمايش هاي موجودي نوع ",
+ "filter-type-entity-view-type-and-name-description": ".شروع مي شود '{{prefix}}' که نامشان با '{{entityView}}' نمايش هاي موجودي نوع",
+ "filter-type-relations-query": "پرس و جو درمورد ارتباطات",
+ "filter-type-relations-query-description": ". دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط {{entities}}",
+ "filter-type-asset-search-query": "پرس و جو درمورد جستجوي دارايي",
+ "filter-type-asset-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{assetTypes}} دارايي ها از انواع",
+ "filter-type-device-search-query": "پرس و چو درمورد جستجوي دستگاه",
+ "filter-type-device-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{deviceTypes}} دستگاه ها از انواع",
+ "filter-type-entity-view-search-query": "پرس و جو درمورد جستجوي نمايش موجودي",
+ "filter-type-entity-view-search-query-description": ".دارند {{direction}} {{rootEntity}} را {{relationType}} که ارتباط{{entityViewTypes}} نمايش هاي موجودي از انواع",
+ "entity-filter": "فيلتر موجودي",
+ "resolve-multiple": "تصميم با توجه به موجودي هاي متعدد",
+ "filter-type": "نوع فيلتر",
+ "filter-type-required": ".نوع فيلتر مورد نياز است",
+ "entity-filter-no-entity-matched": ".هيچ موجودي منطبق بر فيلتر مشخص شده يافت نشد",
+ "no-entity-filter-specified": ".هيچ فيلتر موجودي اي تعيين نشده است",
+ "root-state-entity": "موجودي وضعيت داشبورد به عنوان پايه استفاده شود",
+ "root-entity": "موجودي پايه",
+ "state-entity-parameter-name": "نام پارامتر موجودي وضعيت",
+ "default-state-entity": "موجودي وضعيت پيش فرض",
+ "default-entity-parameter-name": "به صورت پيش فرض",
+ "max-relation-level": "بالاترين سطح ارتباط",
+ "unlimited-level": "سطح نامحدود",
+ "state-entity": "موجودي وضعيت داشبورد",
+ "all-entities": "تمام موجودي ها",
+ "any-relation": "هر"
+ },
+ "asset": {
+ "asset": "دارايي",
+ "assets": "دارايي ها",
+ "management": "مديريت دارايي",
+ "view-assets": "نمايش دارايي ها",
+ "add": "افزودن دارايي",
+ "assign-to-customer": "تخصيص به مشتري",
+ "assign-asset-to-customer": "تخصيص دارايي(ها) به مشتري",
+ "assign-asset-to-customer-text": "لطفا دارايي ها را انتخاب کنيد تا به مشتري تخصيص يابد",
+ "no-assets-text": "هيچ دارايي اي يافت نشد",
+ "assign-to-customer-text": "لطفا مشتري را انتخاب کنيد تا دارايي(ها) تخصيص يابد",
+ "public": "عمومي",
+ "assignedToCustomer": "تخصيص يافته به مشتري",
+ "make-public": "عمومي سازي دارايي",
+ "make-private": "شخصي سازي دارايي",
+ "unassign-from-customer": "لغو تخصيص از مشتري",
+ "delete": "حذف دارايي",
+ "asset-public": "دارايي عمومي است",
+ "asset-type": "نوع دارايي",
+ "asset-type-required": ".نوع دارايي مورد نياز است",
+ "select-asset-type": "انتخاب کردن نوع دارايي",
+ "enter-asset-type": "وارد کردن نوع دارايي",
+ "any-asset": "هر دارايي",
+ "no-asset-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ دارايي منطبق بر",
+ "asset-type-list-empty": ".هيچيک از انواع دارايي انتخاب نشد",
+ "asset-types": "انواع دارايي",
+ "name": "نام",
+ "name-required": ".نام مورد نياز است",
+ "description": "توصيف",
+ "type": "نوع",
+ "type-required": ".نوع مورد نياز است",
+ "details": "جزئيات",
+ "events": "رويدادها",
+ "add-asset-text": "افزودن دارايي جديد",
+ "asset-details": "جزئيات دارايي",
+ "assign-assets": "تخصيص دارايي ها",
+ "assign-assets-text": "به مشتري { count, plural, 1 {1 دارايي} other {# دارايي} } تخصيص",
+ "delete-assets": "حذف دارايي ها",
+ "unassign-assets": "لغو تخصيص دارايي ها",
+ "unassign-assets-action-title": "از مشتري { count, plural, 1 {1 دارايي} other {# دارايي} } لغو تخصيص",
+ "assign-new-asset": "تخصيص دارايي جديد",
+ "delete-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از حذف دارايي",
+ "delete-asset-text": ".مراقب باشيد، پس از تأييد، دارايي و تمام داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "delete-assets-title": "مطمئنيد؟ { count, plural, 1 {1 دارايي} other {# دارايي} } آيا از حذف",
+ "delete-assets-action-title": "{ count, plural, 1 {1 دارايي} other {# دارايي} } حذف",
+ "delete-assets-text": ".مراقب باشيد، پس از تأييد، تمام دارايي هاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "make-public-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از عمومي سازي",
+ "make-public-asset-text": ".پس از تأييد، دارايي و تمامي داده هايش عمومي و قابل دسترسي براي ديگران مي شود",
+ "make-private-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از شخصي سازي دارايي",
+ "make-private-asset-text": ".پس از تأييد، دارايي و تمامي داده هايش شخصي و خارج از دسترس ديگران مي شوند",
+ "unassign-asset-title": "مطمئنيد؟ '{{assetName}}' آيا از لغو تخصيص دارايي",
+ "unassign-asset-text": ".پس از تأييد، دارايي، لغو تخصيص و خارج از دسترس مشتري مي شود",
+ "unassign-asset": "لغو تخصيص دارايي",
+ "unassign-assets-title": "مطمئنيد؟ { count, plural, 1 {1 دارايي} other {# دارايي} } آيا از لغو تخصيص",
+ "unassign-assets-text": ".پس از تأييد، تمام دارايي هاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند",
+ "copyId": "دارايي ID رونوشت از",
+ "idCopiedMessage": "دارايي در حافظه موقت رونوشت شد ID",
+ "select-asset": "انتخاب دارايي",
+ "no-assets-matching": ".يافت نشد '{{entity}}' هيچ دارايي منطبق بر",
+ "asset-required": "دارايي مود نياز است",
+ "name-starts-with": "نام دارايي شروع مي شود با"
+ },
+ "attribute": {
+ "attributes": "ويژگي ها",
+ "latest-telemetry": "آخرين سنجش",
+ "attributes-scope": "حوزه ويژگي هاي موجودي",
+ "scope-latest-telemetry": "آخرين سنجش",
+ "scope-client": "ويژگي هاي مشتري",
+ "scope-server": "ويژگي هاي سِروِر",
+ "scope-shared": "ويژگي هاي مشترک",
+ "add": "افزودن ويژگي ها",
+ "key": "کليد",
+ "last-update-time": "آخرين زمان به روز رساني",
+ "key-required": ".کليد ويژگي مورد نياز است",
+ "value": "مقدار",
+ "value-required": ".مقدار ويژگي مورد نياز است",
+ "delete-attributes-title": "مطمئنيد؟ { count, plural, 1 {1 ويژگي} other {# ويژگي} } آيا از حذف",
+ "delete-attributes-text": ".مراقب باشيد، پس از تأييد، تمام ويژگي هاي انتخاب شده حذف مي گردند",
+ "delete-attributes": "حذف ويژگي ها",
+ "enter-attribute-value": "وارد کردن مقدار ويژگي",
+ "show-on-widget": "نمايش بر ويجت",
+ "widget-mode": "حالت ويجت",
+ "next-widget": "ويجت بعد",
+ "prev-widget": "ويجت قبل",
+ "add-to-dashboard": "افزودن به داشبورد",
+ "add-widget-to-dashboard": "افزودن ويجت به داشبورد",
+ "selected-attributes": "انتخاب شدند { count, plural, 1 {1 ويژگي} other {# ويژگي} }",
+ "selected-telemetry": "انتخاب شد { count, plural, 1 {1 واحد سنجش} other {# واحد سنجش} }"
+ },
+ "audit-log": {
+ "audit": "بازبيني",
+ "audit-logs": "داده هاي ثبت شده از بازبيني",
+ "timestamp": "برچسب زمان",
+ "entity-type": "نوع موحودي",
+ "entity-name": "نام موجودي",
+ "user": "کاربر",
+ "type": "نوع",
+ "status": "وضعيت",
+ "details": "جزئيات",
+ "type-added": "اضافه شده",
+ "type-deleted": "حذف شده",
+ "type-updated": "به روز",
+ "type-attributes-updated": "ويژگي ها به روز شد",
+ "type-attributes-deleted": "ويژگي ها حذف شد",
+ "type-rpc-call": "RPC فراخواني",
+ "type-credentials-updated": "اعتبارنامه ها به روز شد",
+ "type-assigned-to-customer": "به مشتري تخصيص يافت",
+ "type-unassigned-from-customer": "از مشتري لغو تخصيص شد",
+ "type-activated": "فعال شد",
+ "type-suspended": "معلق",
+ "type-credentials-read": "اعتبارنامه ها خوانده شد",
+ "type-attributes-read": "ويژگي ها خوانده شد",
+ "type-relation-add-or-update": "ارتباط به روز شد",
+ "type-relation-delete": "ارتباط حذف شد",
+ "type-relations-delete": "تمام ارتباطات حذف شد",
+ "type-alarm-ack": "تصديق شده",
+ "type-alarm-clear": "پاک شده",
+ "status-success": "موفقيت",
+ "status-failure": "عدم موفقيت",
+ "audit-log-details": "بازبيني جزئيات ثبت داده ها",
+ "no-audit-logs-prompt": "هيچ داده ثبت شده اي يافت نشد",
+ "action-data": "داده هاي اقدام",
+ "failure-details": "جزئيات عدم موفقيت",
+ "search": "جستجوي داده هاي ثبت شده از بازبيني",
+ "clear-search": "پاک کردن جستجو"
+ },
+ "confirm-on-exit": {
+ "message": "شما تغييراتي ذخيره نشده داريد. از ترک اين صفحه مطمئنيد؟",
+ "html-message": "از ترک اين صفحه مطمئنيد؟<br/>.شما تغييراتي ذخيره نشده داريد",
+ "title": "تغييرات ذخيره نشده "
+ },
+ "contact": {
+ "country": "کشور",
+ "city": "شهر",
+ "state": "استان / ايالت",
+ "postal-code": "کد پستي",
+ "postal-code-invalid": ".قالب کد پستي نامعتبر است",
+ "address": "نشاني",
+ "address2": "2 نشاني",
+ "phone": "تلفن",
+ "email": "پست الکترونيک",
+ "no-address": "بدون آدرس"
+ },
+ "common": {
+ "username": "نام کاربري",
+ "password": "رمز عبور",
+ "enter-username": "وارد کردن نام کاربري",
+ "enter-password": "وارد کردن رمز عبور",
+ "enter-search": "وارد کردن جستجو"
+ },
+ "content-type": {
+ "json": "JSON",
+ "text": "Text",
+ "binary": "Binary (Base64)"
+ },
+ "customer": {
+ "customer": "مشتري",
+ "customers": "مشتريان",
+ "management": "مديريت مشتري",
+ "dashboard": "داشبورد مشتري",
+ "dashboards": "داشبوردهاي مشتري",
+ "devices": "دستگاه هاي مشتري",
+ "entity-views": "نمايش موجودي مشتري",
+ "assets": "دارايي هاي مشتري",
+ "public-dashboards": "داشبوردهاي عمومي",
+ "public-devices": "دستگاه هاي عمومي",
+ "public-assets": "دارايي هاي عمومي",
+ "public-entity-views": "نمايش موجودي عمومي",
+ "add": "افزودن مشتري",
+ "delete": "حذف مشتري",
+ "manage-customer-users": "مديريت کاربرهاي مشتري",
+ "manage-customer-devices": "مديريت دستگاه هاي مشتري",
+ "manage-customer-dashboards": "مديريت داشبوردهاي مشتري",
+ "manage-public-devices": "مديريت دستگاه هاي عمومي",
+ "manage-public-dashboards": "مديريت داشبوردهاي عمومي",
+ "manage-customer-assets": "مديريت دارايي هاي مشتري",
+ "manage-public-assets": "مديريت دارايي هاي عمومي",
+ "add-customer-text": "افزودن مشتري جديد",
+ "no-customers-text": "هيچ مشتري اي يافت نشد",
+ "customer-details": "جزئيات اطلاعات مشتري",
+ "delete-customer-title": "مطمئنيد؟ '{{customerTitle}}' از حذف مشتري",
+ "delete-customer-text": ".مراقب باشيد، پس از تأييد، مشتري و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند",
+ "delete-customers-title": "مطمئنيد؟ { count, plural, 1 {1 مشتري} other {# مشتري} } از حذف",
+ "delete-customers-action-title": "{ count, plural, 1 {1 مشتري} other {# مشتري} } حذف",
+ "delete-customers-text": ".مراقب باشيد، پس از تأييد، تمام مشتريانِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل دسترسي مي شوند",
+ "manage-users": "مديريت کاربرها",
+ "manage-assets": "مديريت دارايي ها",
+ "manage-devices": "مديريت دستگاه ها",
+ "manage-dashboards": "مديريت داشبوردها",
+ "title": "عنوان",
+ "title-required": ".عنوان مورد نياز است",
+ "description": "توصيف",
+ "details": "جزئيات",
+ "events": "رويدادها",
+ "copyId": "مشتري ID رونوشت از",
+ "idCopiedMessage": "مشتري در حافظه موقت رونوشت شد ID",
+ "select-customer": "انتخاب مشتري",
+ "no-customers-matching": ".يافت نشد '{{entity}}' هيچ مشتري منطبق بر",
+ "customer-required": "مشتري مورد نياز است",
+ "select-default-customer": "انتخاب مشتري پيش فرض",
+ "default-customer": "مشتري پيش فرض",
+ "default-customer-required": "جهت عيب يابي داشبورد در سطح کاربر مياني، مشتري پيش فرض مورد نياز است"
+ },
+ "datetime": {
+ "date-from": "تاريخ از",
+ "time-from": "زمان از",
+ "date-to": "تاريخ تا",
+ "time-to": "زمان تا"
+ },
+ "dashboard": {
+ "dashboard": "داشبورد",
+ "dashboards": "داشبوردها",
+ "management": "مديريت داشبورد",
+ "view-dashboards": "نمايش داشبوردها",
+ "add": "افزودن داشبورد",
+ "assign-dashboard-to-customer": "تخصيص داشبورد(ها) به مشتري",
+ "assign-dashboard-to-customer-text": "لطفا داشبوردها را، جهت تخصيص به مشتري، انتخاب کنيد",
+ "assign-to-customer-text": "لطفا مشتري را، جهت تخصيص داشبورد(ها)، انتخاب کنيد",
+ "assign-to-customer": "تخصيص به مشتري",
+ "unassign-from-customer": "لغو تخصيص از مشتري",
+ "make-public": "عمومي سازي مشتري",
+ "make-private": "شخصي سازي داشبورد",
+ "manage-assigned-customers": "مديريت مشتريان تخصيص داده شده",
+ "assigned-customers": "مشتريان تخصيص داده شده",
+ "assign-to-customers": "تخصيص داشبورد(ها) به مشتريان",
+ "assign-to-customers-text": "لطفا مشتريان را، جهت تخصيص داشبورد(ها)، انتخاب کنيد",
+ "unassign-from-customers": "لغو تخصيص داشبوردها از مشتريان",
+ "unassign-from-customers-text": "لطفا مشتريان را، جهت لغو تخصيص از داشبورد(ها)، انتخاب کنيد",
+ "no-dashboards-text": "هيچ داشبوردي يافت نشد",
+ "no-widgets": "هيچ ويجتي پيکربندي نشده است",
+ "add-widget": "افزودن ويجت جديد",
+ "title": "عنوان",
+ "select-widget-title": "انتخاب ويجت",
+ "select-widget-subtitle": "ليست انواع ويجت هاي در دسترس",
+ "delete": "حذف داشبورد",
+ "title-required": ".عنوان مورد نياز است",
+ "description": "توصيف",
+ "details": "جزئيات",
+ "dashboard-details": "جزئيات داشبورد",
+ "add-dashboard-text": "افزودن داشبورد جديد",
+ "assign-dashboards": "تخصيص داشبوردها",
+ "assign-new-dashboard": "تخصيص داشبورد جديد",
+ "assign-dashboards-text": "به مشتريان { count, plural, 1 {1 داشبورد} other {# داشبورد} } تخصيص",
+ "unassign-dashboards-action-text": "از مشتريان { count, plural, 1 {1 داشبورد} other {# داشبورد} } لغو تخصيص",
+ "delete-dashboards": "حذف داشبوردها",
+ "unassign-dashboards": "لغو تخصيص داشبوردها",
+ "unassign-dashboards-action-title": "از مشتري { count, plural, 1 {1 داشبورد} other {# داشبورد} } لغو تخصيص",
+ "delete-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از حذف",
+ "delete-dashboard-text": ".مراقب باشيد، پس از تأييد، داشبورد و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند",
+ "delete-dashboards-title": "مطمئنيد؟ { count, plural, 1 {1 داشبورد} other {# داشبورد} } از حذف",
+ "delete-dashboards-action-title": "{ count, plural, 1 {1 داشبورد} other {# داشبورد} } حذف",
+ "delete-dashboards-text": ".مراقب باشيد، پس از تأييد، تمام داشبوردهاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند ",
+ "unassign-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از لغو تخصيص داشبورد",
+ "unassign-dashboard-text": ".پس از تأييد، داشبورد، لغو تخصيص و خارج از دسترس مشتري مي شود",
+ "unassign-dashboard": "لغو تخصيص داشبورد",
+ "unassign-dashboards-title": "مطمئنيد؟ { count, plural, 1 {1 داشبورد} other {# داشبورد} } از لغو تخصيص",
+ "unassign-dashboards-text": ".پس از تأييد، تمام داشبوردهاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند",
+ "public-dashboard-title": "داشبورد اکنون عمومي است",
+ "public-dashboard-text": ":</a>قابل دسترسي است<a href='{{publicLink}}' target='_blank'> اکنون عمومي بوده و از طريق پيوند عمومي ديگر ، <b>{{dashboardTitle}}</b> ،داشبورد شما",
+ "public-dashboard-notice": ".فراموش نکنيد براي دسترسي به داده هاي دستگاه هاي مربوطه، آنها را عمومي نماييد </b>:توجه<b>",
+ "make-private-dashboard-title": "مطمئنيد؟ '{{dashboardTitle}}' از شخصي سازي داشبورد",
+ "make-private-dashboard-text": ".پس از تأييد، داشبورد، شخصي و خارج از دسترس ديگران مي شود",
+ "make-private-dashboard": "شخصي سازي داشبورد",
+ "socialshare-text": "ThingsBoard طراحي شده توسط '{{dashboardTitle}}'",
+ "socialshare-title": "ThingsBoard طراحي شده توسط '{{dashboardTitle}}'",
+ "select-dashboard": "انتخاب داشبورد",
+ "no-dashboards-matching": ".يافت نشد '{{entity}}' هيچ داشبوردي منطبق بر",
+ "dashboard-required": ".داشبورد مورد نياز است",
+ "select-existing": "انتخاب داشبورد موجود",
+ "create-new": "ايجاد داشبورد جديد",
+ "new-dashboard-title": "عنوان داشبورد جديد",
+ "open-dashboard": "باز کردن داشبورد",
+ "set-background": "تنظيم پس زمينه",
+ "background-color": "رنگ پس زمينه",
+ "background-image": "تصوير پس زمينه",
+ "background-size-mode": "حالت اندازه پس زمينه",
+ "no-image": "هيچ تصويري انتخاب نشد",
+ "drop-image": ".جهت بارگذاري يک تصوير، آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد",
+ "settings": "تنظيمات",
+ "columns-count": "شمارش ستون ها",
+ "columns-count-required": ".شمارش ستون ها مورد نياز است",
+ "min-columns-count-message": ".کمترين تعداد مجاز ستون ها 10 عدد است",
+ "max-columns-count-message": ".بيشترين تعداد مجاز ستون ها 1000 عدد است",
+ "widgets-margins": "حاشيه بين ويجت ها",
+ "horizontal-margin": "حاشيه افقي",
+ "horizontal-margin-required": ".مقدار حاشيه افقي مورد نياز است",
+ "min-horizontal-margin-message": ".کمترين مقدار مجاز حاشيه افقي 0 است",
+ "max-horizontal-margin-message": ".بيشترين مقدار مجاز حاشيه افقي 50 است",
+ "vertical-margin": "حاشيه عمودي",
+ "vertical-margin-required": ".حاشيه عمودي مورد نياز است",
+ "min-vertical-margin-message": ".کمترين مقدار مجاز حاشيه عمودي 0 است",
+ "max-vertical-margin-message": ".بيشترين مقدار مجاز حاشيه افقي 50 است",
+ "autofill-height": "تنظيم خودکار ارتفاع چيدمان طرح",
+ "mobile-layout": "تنظيمات چيدمان طرح در تلفن همراه",
+ "mobile-row-height": "(px) ارتفاع رديف در تلفن همراه",
+ "mobile-row-height-required": ".مقدار ارتفاع ردبف در تلفن همراه مورد نياز است",
+ "min-mobile-row-height-message": ".کمترين مقدار مجاز ارتفاع رديف در تلفن همراه 5 پيکسل است",
+ "max-mobile-row-height-message": ".بيشترين مقدار مجاز ارتفاع رديف در تلفن همراه 200 پيکسل است",
+ "display-title": "نمايش عنوان داشبورد",
+ "toolbar-always-open": "باز نگه داشتن نوار ابزار",
+ "title-color": "رنگ عنوان",
+ "display-dashboards-selection": "نمايش انتخاب داشبوردها",
+ "display-entities-selection": "نمايش انتخاب موجودي ها",
+ "display-dashboard-timewindow": "نمايش پنجره زمان",
+ "display-dashboard-export": "نمايش صدور",
+ "import": "وارد کردن داشبورد",
+ "export": "صادر کردن داشبورد",
+ "export-failed-error": "{{error}} :صدور داشبورد ممکن نيست",
+ "create-new-dashboard": "ايجاد داشبورد جديد",
+ "dashboard-file": "پرونده داشبورد",
+ "invalid-dashboard-file-error": ".وارد کردن داشبورد ممکن نيست: ساختار داده داشبورد نامعتبر است",
+ "dashboard-import-missing-aliases-title": "پيکربندي نامهاي مستعار استفاده شده توسط داشبوردِ وارده",
+ "create-new-widget": "ايجاد ويجت جديد",
+ "import-widget": "وارد کردن ويجت",
+ "widget-file": "پرونده ويجت",
+ "invalid-widget-file-error": ".وارد کردن ويجت ممکن نيست: ساختار داده ويجت نامعتبر است",
+ "widget-import-missing-aliases-title": "پيکربندي نامهاي مستعار استفاده شده توسط ويجتِ وارده",
+ "open-toolbar": "باز کردن نوار ابزار داشبورد",
+ "close-toolbar": "بستن نوار ابزار",
+ "configuration-error": "خطاي پيکربندي",
+ "alias-resolution-error-title": "خطاي پيکربندي نامهاي مستعار داشبورد",
+ "invalid-aliases-config": ".لطفا جهت حل اين موضوع با مسئول مربوط به خود تماس بگيريد<br/>.يافتن دستگاهي منطبق بر فبلتر بعضي نامهاي مستعار ممکن نيست",
+ "select-devices": "انتخاب دستگاه ها",
+ "assignedToCustomer": "تخصيص يافته به مشتري",
+ "assignedToCustomers": "تخصيص يافته به مشتريان",
+ "public": "عمومي",
+ "public-link": "پيوند عمومي",
+ "copy-public-link": "رونوشت از پيوند عمومي",
+ "public-link-copied-message": "پيوند عمومي داشبورد در حافظه موقت رونوشت شد",
+ "manage-states": "مديريت وضعيت هاي داشبورد",
+ "states": "وضعيت هاي داشبورد",
+ "search-states": "جستجوي وضعيت هاي داشبورد",
+ "selected-states": "انتخاب شدند { count, plural, 1 {1 وضعيت داشبورد} other {# وضعيت داشبورد} }",
+ "edit-state": "ويرايش وضعيت داشبورد",
+ "delete-state": "حذف وضعيت داشبورد",
+ "add-state": "افزودن وضعيت داشبورد",
+ "state": "وضعيت داشبورد",
+ "state-name": "نام",
+ "state-name-required": ".نام وضعيت داشبورد مورد نياز است",
+ "state-id": "وضعيت ID",
+ "state-id-required": ".وضعيت داشبورد مورد نياز است ID",
+ "state-id-exists": ".مشابه موجود است ID در حال حاضر وضعيت داشبوردي با",
+ "is-root-state": "وضعيت پايه",
+ "delete-state-title": "حذف وضعيت داشبورد",
+ "delete-state-text": "مطمئنيد؟ '{{stateName}}' از حذف وضعيت داشبورد با نام",
+ "show-details": "نمايش جزئيات",
+ "hide-details": "پنهان کردن جزئيات",
+ "select-state": "انتخاب وضعيت هدف",
+ "state-controller": "کنترل کننده وضعيت"
+ },
+ "datakey": {
+ "settings": "تنظيمات",
+ "advanced": "پيشرفته",
+ "label": "برچسب",
+ "color": "رنگ",
+ "units": "کارکتر خاص براي نمايش بعد از مقدار تعين شده",
+ "decimals": "تعداد ارقام بعد از مميّز شناور",
+ "data-generation-func": "تابع توليد داده",
+ "use-data-post-processing-func": "استفاده از تابع پس پردازش داده",
+ "configuration": "پيکربندي کليد داده",
+ "timeseries": "سري هاي زماني",
+ "attributes": "ويژگي ها",
+ "alarm": "حوزه هاي هشدار",
+ "timeseries-required": ".سري هاي زماني موجودي مورد نياز است",
+ "timeseries-or-attributes-required": ".سري هاي زماني / ويژگي هاي موجودي مورد نياز است",
+ "maximum-timeseries-or-attributes": "{ count, plural, 1 {.1 سري زماني / ويژگي مجاز است} other {# سري زماني / ويژگي مجازند} } بيشترين",
+ "alarm-fields-required": ".حوزه هاي هشدار مورد نياز است",
+ "function-types": "نوع توابع",
+ "function-types-required": ".نوع تابع مورد نياز است",
+ "maximum-function-types": "{ count, plural, 1 {.1 نوع تابع مجاز است} other {# نوع تابع مجازند} } بيشترين",
+ "time-description": "برچسب زماني مقدار فعلي؛",
+ "value-description": "مقدار فعلي؛",
+ "prev-value-description": "نتيجه ي فراخوانيِ تابع قبلي؛",
+ "time-prev-description": "برچسب زماني مقدار قبلي؛",
+ "prev-orig-value-description": "ممقدار اصلي قبلي"
+ },
+ "datasource": {
+ "type": "نوع منبع داده",
+ "name": "نام",
+ "add-datasource-prompt": "لطفا منبع داده را اضافه کنيد"
+ },
+ "details": {
+ "edit-mode": "حالت ويرايش",
+ "toggle-edit-mode": "حالت ويرايش را تغيير دهيد"
+ },
+ "device": {
+ "device": "دستگاه",
+ "device-required": ".دستگاه مورد نياز است",
+ "devices": "دستگاه ها",
+ "management": "مديريت دستگاه",
+ "view-devices": "نمايش دستگاه ها",
+ "device-alias": "نام مستعار دستگاه",
+ "aliases": "نامهاي مستعار دستگاه",
+ "no-alias-matching": ".يافت نشد'{{alias}}'",
+ "no-aliases-found": ".هيچ نام مستعاري يافت نشد",
+ "no-key-matching": ".يافت نشد'{{key}}'",
+ "no-keys-found": ".هيچ کليدي يافت نشد",
+ "create-new-alias": "!ايجاد يک نام مستعار جديد",
+ "create-new-key": "!ايجاد يک کليد جديد",
+ "duplicate-alias-error": ".نام مستعار در داشبورد بايد يکتا باشد<br>'{{alias}}'نام مستعار مشابه يافت شد",
+ "configure-alias": "نام مستعار '{{alias}}' پيکربندي",
+ "no-devices-matching": "مطابقت داشته باشد وجود ندارد '{{entity}}' هيچ دستگاهي که با ",
+ "alias": "نام مستعار",
+ "alias-required": ".نام مستعار مورد نياز است",
+ "remove-alias": "حذف نام مستعار دستگاه",
+ "add-alias": "افزودن نام مستعار دستگاه",
+ "name-starts-with": "اسم دستگاه شروع مي شود با",
+ "device-list": "ليست دستگاه ها",
+ "use-device-name-filter": "از فيلتر استفاده کنيد",
+ "device-list-empty": ".هيچ دستگاهي انتخاب نشده است",
+ "device-name-filter-required": ".فيلتر نام دستگاه مورد نياز است",
+ "device-name-filter-no-device-matched": ".شروع شود يافت نشد '{{device}}' هيچ دستگاهي که با",
+ "add": "افزودن دستگاه",
+ "assign-to-customer": "تخصيص به مشتري",
+ "assign-device-to-customer": "تخصيص دستگاه (ها) به مشتري",
+ "assign-device-to-customer-text": "لطفا دستگاه ها را انتخاب کنيد تا به مشتري تخصيص يابد",
+ "make-public": "عمومي سازي دستگاه",
+ "make-private": "شخصي سازي دستگاه",
+ "no-devices-text": "هيچ دستگاهي يافت نشد",
+ "assign-to-customer-text": "لطفا مشتري را انتخاب کنيد تا دستگاه(ها) تخصيص يابد",
+ "device-details": "جزئيات دستگاه",
+ "add-device-text": "افزودن دستگاه جديد",
+ "credentials": "اعتبارنامه ها",
+ "manage-credentials": "مديريت اعتبارنامه ها",
+ "delete": "حذف دستگاه",
+ "assign-devices": "تخصيص دستگاه ها",
+ "assign-devices-text": "به مشتري { count, plural, 1 {1 دستگاه} other {# دستگاه} } تخصيص",
+ "delete-devices": "حذف دستگاه ها",
+ "unassign-from-customer": "لغو تخصيص از مشتري",
+ "unassign-devices": "لغو تخصيص دستگاه ها",
+ "unassign-devices-action-title": "از مشتري { count, plural, 1 {1 دستگاه} other {# دستگاه} } لغو تخصيص",
+ "assign-new-device": "تخصيص دستگاه جديد",
+ "make-public-device-title": "مطمئنيد؟ '{{deviceName}}' از عمومي سازي دستگاه",
+ "make-public-device-text": ".پس از تأييد، دستگاه و تمامي داده هايش عمومي و قابل دسترسي براي ديگران مي شود",
+ "make-private-device-title": "مطمئنيد؟ '{{deviceName}}' از شخصي سازي دستگاه",
+ "make-private-device-text": ".پس از تأييد، دستگاه و تمامي داده هايش شخصي و خارج از دسترس ديگران مي شوند",
+ "view-credentials": "نمايش اعتبارنامه ها",
+ "delete-device-title": "مطمئنيد؟ '{{deviceName}}' از حذف",
+ "delete-device-text": ".مراقب باشيد، پس از تأييد، دستگاه و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "delete-devices-title": "مطمئنيد؟ { count, plural, 1 {1 دستگاه} other {# دستگاه} } از حذف",
+ "delete-devices-action-title": "{ count, plural, 1 {1 دستگاه} other {# دستگاه} } حذف",
+ "delete-devices-text": ".مراقب باشيد، پس از تأييد، تمام دستگاه هاي انتخاب شده، حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "unassign-device-title": "مطمئنيد؟ '{{deviceName}}' از لغو تخصيص",
+ "unassign-device-text": ".پس از تأييد، دستگاه، لغو تخصيص و خارج از دسترس مشتري مي شود",
+ "unassign-device": "لغو تخصيص دستگاه",
+ "unassign-devices-title": "مطمئنيد؟ { count, plural, 1 {1 دستگاه} other {# دستگاه} } از لغو تخصيص",
+ "unassign-devices-text": ".پس از تأييد، تمام دستگاه هاي انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند",
+ "device-credentials": "اعتبارنامه هاي دستگاه",
+ "credentials-type": "نوع اعتبارنامه ها",
+ "access-token": "شناسه دسترسي",
+ "access-token-required": ".شناسه دسترسي مورد نياز است",
+ "access-token-invalid": ".طول شناسه دسترسي بايد از 1 تا 20 حرف باشد",
+ "rsa-key": "RSA کليد عمومي",
+ "rsa-key-required": ".مورد نياز است RSA کليد عمومي",
+ "secret": "محرمانه",
+ "secret-required": ".شناسه محرمانه مورد نياز است",
+ "device-type": "نوع دستگاه",
+ "device-type-required": ".نوع دستگاه مورد نياز است",
+ "select-device-type": "انتخاب نوع دستگاه",
+ "enter-device-type": "وارد کردن نوع دستگاه",
+ "any-device": "هر دستگاهي",
+ "no-device-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ نوع دستگاهي منطبق بر",
+ "device-type-list-empty": ".هيچ نوع دستگاهي انتخاب نشد",
+ "device-types": "انواع دستگاه",
+ "name": "نام",
+ "name-required": ".نام مورد نياز است",
+ "description": "توصيف",
+ "events": "رويدادها",
+ "details": "جزئيات",
+ "copyId": "دستگاه ID رونوشت از",
+ "copyAccessToken": "رونوشت از شناسه دسترسي",
+ "idCopiedMessage": ".دستگاه در حافظه موقت رونوشت شد ID",
+ "accessTokenCopiedMessage": ".شناسه دسترسي دستگاه در حافظه موقت رونوشت شد",
+ "assignedToCustomer": "تخصيص يافته به مشتري",
+ "unable-delete-device-alias-title": "حذف نام مستعار دستگاه ممکن نيست",
+ "unable-delete-device-alias-text": "<br/>{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{deviceAlias}}' ،نام مستعار دستگاه",
+ "is-gateway": "درگاه است",
+ "public": "عمومي",
+ "device-public": "دستگاه عمومي است",
+ "select-device": "انتخاب دستگاه"
+ },
+ "dialog": {
+ "close": "بستن گفتگو"
+ },
+ "error": {
+ "unable-to-connect": ".اتصال به سِروِر ممکن نيست! لطفا اتصال اينترنت خود را بررسي کنيد",
+ "unhandled-error-code": "{{errorCode}} :کد خطاي رسيدگي نشده",
+ "unknown-error": "خطاي ناشناخته"
+ },
+ "entity": {
+ "entity": "موجودي",
+ "entities": "موجودي ها",
+ "aliases": "نامهاي مستعار موجودي",
+ "entity-alias": "نام مستعار موجودي",
+ "unable-delete-entity-alias-title": "حذف نام مستعار موجودي ممکن نيست",
+ "unable-delete-entity-alias-text": "<br/>{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{entityAlias}}' ،نام مستعار موجودي",
+ "duplicate-alias-error": ".نامهاي مستعار موجودي بايد در داشبورد، منحصر بفرد باشند<br>.يافت شد '{{alias}}' نام مستعار تکراري",
+ "missing-entity-filter-error": ".مفقود است '{{alias}}' فيلتر براي نام مستعار",
+ "configure-alias": "'{{alias}}' پيکربندي نام مستعار",
+ "alias": "نام مستعار",
+ "alias-required": ".نام مستعار موجودي مورد نياز است",
+ "remove-alias": "حذف نام مستعار موجودي",
+ "add-alias": "افزودن نام مستعار موجودي",
+ "entity-list": "ليست موجودي",
+ "entity-type": "نوع موجودي",
+ "entity-types": "انواع موجودي",
+ "entity-type-list": "ليست نوع موجودي",
+ "any-entity": "هر موجودي",
+ "enter-entity-type": "وارد کردن نوع موجودي",
+ "no-entities-matching": ".يافت نشد '{{entity}}' هيچ موجودي منطبق بر",
+ "no-entity-types-matching": ".يافت نشد '{{entityType}}' هيچ نوع موجودي منطبق بر",
+ "name-starts-with": "نام شروع مي شود با",
+ "use-entity-name-filter": "استفاده از فيلتر",
+ "entity-list-empty": ".هيچ موجودي اي انتخاب نشده است",
+ "entity-type-list-empty": ".هيچ نوع موجودي انتخاب نشده است",
+ "entity-name-filter-required": ".فيلتر نام موجودي مورد نياز است",
+ "entity-name-filter-no-entity-matched": ".شروع شود يافت نشد '{{entity}}' هيچ موجودي که با",
+ "all-subtypes": "همه",
+ "select-entities": "انتخاب موجودي ها",
+ "no-aliases-found": ".هيچ نام مستعاري يافت نشد",
+ "no-alias-matching": ".يافت نشد '{{alias}}'",
+ "create-new-alias": "!ايجاد يک نام مستعار جديد",
+ "key": "کليد",
+ "key-name": "نام کليد",
+ "no-keys-found": ".هيچ کليدي يافت نشد",
+ "no-key-matching": "'.يافت نشد {{key}}'",
+ "create-new-key": "!ايجاد يک کليد جديد",
+ "type": "نوع",
+ "type-required": ".نوع موجودي مورد نياز است",
+ "type-device": "دستگاه",
+ "type-devices": "دستگاه ها",
+ "list-of-devices": "{ count, plural, 1 {يک دستگاه} other {ليست # دستگاه} }",
+ "device-name-starts-with": "شروع مي شود '{{prefix}}' دستگاه هايي که نامشان با",
+ "type-asset": "دارايي",
+ "type-assets": "دارايي ها",
+ "list-of-assets": "{ count, plural, 1 {يک دارايي} other {ليست # دارايي} }",
+ "asset-name-starts-with": "شروع مي شود '{{prefix}}' دارايي هايي که نامشان با",
+ "type-entity-view": "نمايش موجودي",
+ "type-entity-views": "نمايش هاي موجودي",
+ "list-of-entity-views": "{ count, plural, 1 {يک نمايش موجودي} other {ليست # نمايش موجودي} }",
+ "entity-view-name-starts-with": "شروع مي شود '{{prefix}}' نمايش هاي موجودي که نامشان با",
+ "type-rule": "قاعده",
+ "type-rules": "قواعد",
+ "list-of-rules": "{ count, plural, 1 {يک قاعده} other {ليست # قاعده} }",
+ "rule-name-starts-with": "شروع مي شود '{{prefix}}' قواعدي که نامشان با",
+ "type-plugin": "ابزار جانبي",
+ "type-plugins": "ابزارهاي جانبي",
+ "list-of-plugins": "{ count, plural, 1 {يک ابزار جانبي} other {ليست # ابزار جانبي} }",
+ "plugin-name-starts-with": "شروع مي شود '{{prefix}}' ابزارهاي جانبي که نامشان با",
+ "type-tenant": "کاربر",
+ "type-tenants": "کاربران",
+ "list-of-tenants": "{ count, plural, 1 {يک کاربر} other {ليست # کاربر} }",
+ "tenant-name-starts-with": "شروع مي شود '{{prefix}}' کاربرهايي که نامشان با",
+ "type-customer": "مشتري",
+ "type-customers": "مشتريان",
+ "list-of-customers": "{ count, plural, 1 {يک مشتري} other {ليست # مشتري} }",
+ "customer-name-starts-with": "شروع مي شود '{{prefix}}' مشترياني که نامشان با",
+ "type-user": "کاربر",
+ "type-users": "کاربران",
+ "list-of-users": "{ count, plural, 1 {يک کاربر} other {ليست # کاربر} }",
+ "user-name-starts-with": "شروع مي شود '{{prefix}}' کاربرهايي که نامشان با",
+ "type-dashboard": "داشبورد",
+ "type-dashboards": "داشبوردها",
+ "list-of-dashboards": "{ count, plural, 1 {يک داشبورد} other {ليست # داشبورد} }",
+ "dashboard-name-starts-with": "شروع مي شود '{{prefix}}' داشبوردهايي که نامشان با",
+ "type-alarm": "هشدار",
+ "type-alarms": "هشدارها",
+ "list-of-alarms": "{ count, plural, 1 {يک هشدار} other {ليست # هشدار} }",
+ "alarm-name-starts-with": "شروع مي شود '{{prefix}}' هشدارهايي که نامشان با",
+ "type-rulechain": "زنجيره قواعد",
+ "type-rulechains": "زنجيره هاي قواعد",
+ "list-of-rulechains": "{ count, plural, 1 {يک زنجيره قواعد} other {ليست # زنجيره قواعد} }",
+ "rulechain-name-starts-with": "شروع مي شود '{{prefix}}' زنجيره هاي قواعدي که نامشان با",
+ "type-rulenode": "گره قواعد",
+ "type-rulenodes": "گره هاي قواعد",
+ "list-of-rulenodes": "{ count, plural, 1 {يک گره قواعد} other {ليست # گره قواعد} }",
+ "rulenode-name-starts-with": "شروع مي شود '{{prefix}}' گره هاي قواعدي که نامشان با",
+ "type-current-customer": "مشتري فعلي",
+ "search": "جستجوي موجودي ها",
+ "selected-entities": "انتخاب شدند { count, plural, 1 {1 موجودي} other {# موجودي} }",
+ "entity-name": "نام موجودي",
+ "details": "جزئيات موجودي",
+ "no-entities-prompt": "هيچ موجودي اي يافت نشد",
+ "no-data": "هيچ داده اي براي نمايش نيست",
+ "columns-to-display": "ستون ها براي نمايش"
+ },
+ "entity-view": {
+ "entity-view": "نمايش موجودي",
+ "entity-view-required": ".نمايش موجودي مورد نياز است",
+ "entity-views": "نمايش هاي موجودي",
+ "management": "مديريت نمايش موجودي",
+ "view-entity-views": "نمايش نمايش هاي موجودي",
+ "entity-view-alias": "نام مستعار نمايش موجودي",
+ "aliases": "نامهاي مستعار نمايش موجودي",
+ "no-alias-matching": ".يافت نشد '{{alias}}'",
+ "no-aliases-found": ".هيچ نام مستعاري يافت نشد",
+ "no-key-matching": ".يافت نشد '{{key}}'",
+ "no-keys-found": ".هيچ کليدي يافت نشد",
+ "create-new-alias": "!ايجاد نام مستعار جديد",
+ "create-new-key": "!ايجاد کليد جديد",
+ "duplicate-alias-error": ".نامهاي مستعار نمايش موجودي بايد در داشبورد، منحصر بفرد باشند<br>.يافت شد '{{alias}}' نام مستعار تکراري",
+ "configure-alias": "'{{alias}}' پيکربندي نام مستعار",
+ "no-entity-views-matching": ".يافت نشد '{{entity}}' هيچ موجودي منطبق بر",
+ "alias": "نام مستعار",
+ "alias-required": ".نام مستعار نمايش موجودي مورد نياز است",
+ "remove-alias": "حذف نام مستعار نمايش موجودي",
+ "add-alias": "افزودن نام مستعار نمايش موجودي",
+ "name-starts-with": "نام نمايش موجودي شروع مي شود با",
+ "entity-view-list": "ليست نمايش موجودي",
+ "use-entity-view-name-filter": "استفاده از فيلتر",
+ "entity-view-list-empty": ".هيچ نمايش موجودي انتخاب نشد",
+ "entity-view-name-filter-required": ".فيلتر نام نمايش موجودي مورد نياز است",
+ "entity-view-name-filter-no-entity-view-matched": ".شروع شود يافت نشد '{{entityView}}' هيچ نمايش موجودي که با",
+ "add": "افزودن نمايش موجودي",
+ "assign-to-customer": "تخصيص به مشتري",
+ "assign-entity-view-to-customer": "تخصيص نمايش(هاي) موجودي به مشتري",
+ "assign-entity-view-to-customer-text": ".لطفا نمايش هاي موجودي را انتخاب کنيد تا به مشتري تخصيص يابند",
+ "no-entity-views-text": "هيچ نمايش موجودي يافت نشد",
+ "assign-to-customer-text": ".لطفا مشتري را انتخاب کنيد تا نمايش(هاي) موجودي تخصيص يابد",
+ "entity-view-details": "جزئيات نمايش موجودي",
+ "add-entity-view-text": "افزودن نمايش موجودي جديد",
+ "delete": "حذف نمايش موجودي",
+ "assign-entity-views": "تخصيص نمايش هاي موجودي",
+ "assign-entity-views-text": "به مشتري { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } تخصيص",
+ "delete-entity-views": "حذف نمايش هاي موجودي",
+ "unassign-from-customer": "لغو تخصيص از مشتري",
+ "unassign-entity-views": "لغو تخصيص نمايش هاي موجودي",
+ "unassign-entity-views-action-title": "از مشتري { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } لغو تخصيص",
+ "assign-new-entity-view": "تخصيص نمايش موجودي جديد",
+ "delete-entity-view-title": "مطمئنيد؟ '{{entityViewName}}' از حذف نمايش موجودي",
+ "delete-entity-view-text": ".مراقب باشيد، پس از تأييد، نمايش موجودي و تمامي داده هاي مربوطه، غير قابل بازيابي مي شوند",
+ "delete-entity-views-title": "مطمئنيد؟ { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } از حذف نمايش موجودي",
+ "delete-entity-views-action-title": "{ count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } حذف",
+ "delete-entity-views-text": ".مراقب باشيد، پس از تأييد، تمام نمايش هاي موجوديِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "unassign-entity-view-title": "مطمئنيد؟ '{{entityViewName}}' از لغو تخصيص نمايش موجودي",
+ "unassign-entity-view-text": ".پس از تأييد، نمايش موجودي، لغو تخصيص و خارج از دسترس مشتري مي شود",
+ "unassign-entity-view": "لغو تخصيص نمايش موجودي",
+ "unassign-entity-views-title": "مطمئنيد؟ { count, plural, 1 {1 نمايش موجودي} other {# نمايش موجودي} } از لغو تخصيص",
+ "unassign-entity-views-text": ".پس از تأييد، تمام نمايش هاي موجوديِ انتخاب شده، لغو تخصيص و خارج از دسترس مشتري مي شوند",
+ "entity-view-type": "نوع نمايش موجودي",
+ "entity-view-type-required": ".نوع نمايش موجودي مورد نياز است",
+ "select-entity-view-type": "انتخاب نوع نمايش موجودي",
+ "enter-entity-view-type": "وارد کردن نوع نمايش موجودي",
+ "any-entity-view": "هر نمايش موجودي",
+ "no-entity-view-types-matching": ".يافت نشد '{{entitySubtype}}' هيچ نوع نمايش موجودي منطبق بر",
+ "entity-view-type-list-empty": ".هيچ نوع نمايش موجودي انتخاب نشد",
+ "entity-view-types": "انواع نمايش موجودي",
+ "name": "نام",
+ "name-required": ".نام مورد نياز است",
+ "description": "توصيف",
+ "events": "رويدادها",
+ "details": "جزئيات",
+ "copyId": "نمايش موجودي ID رونوشت از",
+ "assignedToCustomer": "تخصيص يافته به مشتري",
+ "unable-entity-view-device-alias-title": ".حذف نام مستعار نمايش موجودي ممکن نيست",
+ "unable-entity-view-device-alias-text": "<br/>{{widgetsList}} :را تا زمان استفاده توسط ويجت(هاي) زير نمي توان حذف کرد ، '{{entityViewAlias}}' ،نام مستعار دستگاه",
+ "select-entity-view": "انتخاب نمايش موجودي",
+ "make-public": "عمومي سازي نمايش موجودي",
+ "start-date": "تاريخ شروع",
+ "start-ts": "زمان شروع",
+ "end-date": "تاريخ پايان",
+ "end-ts": "زمان پايان",
+ "date-limits": "محدوده تاريخ",
+ "client-attributes": "ويژگي هاي مشتري",
+ "shared-attributes": "ويژگي هاي مشترک",
+ "server-attributes": "ويژگي هاي سِروِر",
+ "timeseries": "سري هاي زماني",
+ "client-attributes-placeholder": "ويژگي هاي مشتري",
+ "shared-attributes-placeholder": "ويژگي هاي مشترک",
+ "server-attributes-placeholder": "ويژگي هاي سِروِر",
+ "timeseries-placeholder": "سري هاي زماني",
+ "target-entity": "موجودي هدف",
+ "attributes-propagation": "انتشار ويژگي ها",
+ "attributes-propagation-hint": "هر بار که شما نمايش موجودي را بروز رساني يا ذخيره مي کنيد، نمايش موجودي بصور خودکار ويژگي هاي تعيين شده را از موجودي هدف کپي مي کند و به دلايل عملکردي، ويژگي هاي موجودي هدف، با هر بار تغيير ويژگي، در نمايش موجودي انتشار نمي يابند. مي توانيد با پيکربندي گره قواعد در زنجيره قواعد خود و پيوند دهي \"Post attributes\" و \"Attributes Updated\" آن به گره قواعد جديد، انتشار خودکار \"copy to view\" را ممکن سازيد ." ,
+ "timeseries-data": "داده ي سري هاي زماني",
+ "timeseries-data-hint": "کليدهاي داده ي سري هاي زمانيِ موجوديِ هدف را پيکربندي کنيد تا در دسترسِ نمايش موجودي باشند. اين سري هاي زماني، فقط خواندني است"
+ },
+ "event": {
+ "event-type": "نوع رويداد",
+ "type-error": "خطا",
+ "type-lc-event": "رويداد چرخه عمر",
+ "type-stats": "آمار",
+ "type-debug-rule-node": "اشکال زدايي",
+ "type-debug-rule-chain": "اشکال زدايي",
+ "no-events-prompt": "هيچ رويدادي يافت نشد",
+ "error": "خطا",
+ "alarm": "هشدار",
+ "event-time": "زمان رويداد",
+ "server": "سِروِر",
+ "body": "بدنه",
+ "method": "روش",
+ "type": "نوع",
+ "entity": "موجودي",
+ "message-id": "پيام ID",
+ "message-type": "نوع پيام",
+ "data-type": "نوع داده",
+ "relation-type": "نوع ارتباط",
+ "metadata": "فرا داده",
+ "data": "داده",
+ "event": "رويداد",
+ "status": "وضعيت",
+ "success": "موفقيت",
+ "failed": "عدم موفقيت",
+ "messages-processed": "پيام پردازش شد",
+ "errors-occurred": "خطاها رخ دادند"
+ },
+ "extension": {
+ "extensions": "دنباله ها",
+ "selected-extensions": "انتخاب شدند { count, plural, 1 {1 افزونه} other {افزونه ها #} }",
+ "type": "نوع",
+ "key": "کليد",
+ "value": "مقدار",
+ "id": "ID",
+ "extension-id": " افزونه ID",
+ "extension-type": "نوع افزونه",
+ "transformer-json": "JSON *",
+ "unique-id-required": ".افزونه فعلي موجود است ID در حال حاضر",
+ "delete": "حذف دنباله",
+ "add": "افزودن دنباله",
+ "edit": "ويرايش دنباله",
+ "delete-extension-title": "مطمئنيد؟ '{{extensionId}}' از حذف افزونه",
+ "delete-extension-text": ".مراقب باشيد، پس از تأييد، افزونه و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "delete-extensions-title": "مطمئنيد؟ { count, plural, 1 {1 افزونه} other {# افزونه} } از حذف",
+ "delete-extensions-text": ".مراقب باشيد، پس از تأييد، تمام افزونه هاي انتخاب شده حذف مي گردند",
+ "converters": "مبدّل ها",
+ "converter-id": "مبدّل ID",
+ "configuration": "پيکربندي",
+ "converter-configurations": "پيکربندي هاي مبدّل",
+ "token": "نشانه امنيت",
+ "add-converter": "افزودن مبدّل",
+ "add-config": "افزودن پيکربندي مبدّل",
+ "device-name-expression": "عبارت نام دستگاه",
+ "device-type-expression": "عبارت نوع دستگاه",
+ "custom": "متداول",
+ "to-double": "دو برابر شدن",
+ "transformer": "مبدّل",
+ "json-required": ".مبدّل مورد نياز است JSON",
+ "json-parse": ".مبدّل ممکن نيست JSON تجزيه",
+ "attributes": "ويژگي ها",
+ "add-attribute": "افزودن ويژگي",
+ "add-map": "افزودن جزء نگاشت",
+ "timeseries": "سري هاي زماني",
+ "add-timeseries": "افزودن سري هاي زماني",
+ "field-required": "دامنه مورد نياز است",
+ "brokers": "واسطه ها",
+ "add-broker": "افزودن واسطه",
+ "host": "ميزبان",
+ "port": "درگاه",
+ "port-range": ".درگاه بايد در بازه اي بين 1 تا 65535 باشد",
+ "ssl": "Ssl",
+ "credentials": "اعتبارنامه ها",
+ "username": "نام کاربري",
+ "password": "رمز عبور",
+ "retry-interval": "بازخواني فاصله در ميلي ثانيه",
+ "anonymous": "بي نام",
+ "basic": "پايه",
+ "pem": "PEM",
+ "ca-cert": "CA پرونده گواهينامه *",
+ "private-key": "پرونده کليد شخصي *",
+ "cert": "پرونده گواهينامه *",
+ "no-file": ".هيچ پرونده اي انتخاب نشد",
+ "drop-file": ".جهت بارگذاري يک پرونده، آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد",
+ "mapping": "نگاشت",
+ "topic-filter": "فيلتر عنوان",
+ "converter-type": "نوع مبدّل",
+ "converter-json": "JSON",
+ "json-name-expression": "نام دستگاه JSON عبارت",
+ "topic-name-expression": "عبارت عنوان نام دستگاه",
+ "json-type-expression": "نوع دستگاه JSON عبارت",
+ "topic-type-expression": "عبارت عنوان نوع دستگاه",
+ "attribute-key-expression": "عبارت کليد ويژگي",
+ "attr-json-key-expression": "کليد ويژگي JSON عبارت",
+ "attr-topic-key-expression": "عبارت عنوان کليد ويژگي",
+ "request-id-expression": "ID درخواست عبارت",
+ "request-id-json-expression": "ID JSON درخواست عبارت",
+ "request-id-topic-expression": "ID درخواست عبارت عنوان",
+ "response-topic-expression": "عبارت عنوان پاسخ",
+ "value-expression": "عبارت مقدار",
+ "topic": "عنوان",
+ "timeout": "وقفه در ميلي ثانيه",
+ "converter-json-required": ".مبدّل مورد نياز است JSON",
+ "converter-json-parse": ".مبدّل ممکن نيست JSON تجزيه",
+ "filter-expression": "عبارت فيلتر",
+ "connect-requests": "درخواست هاي اتصال",
+ "add-connect-request": "افزودن درخواست اتصال",
+ "disconnect-requests": "درخواست هاي قطع اتصال",
+ "add-disconnect-request": "افزودن درخواست قطع اتصال",
+ "attribute-requests": "درخواست هاي ويژگي",
+ "add-attribute-request": "افزودن درخواست ويژگي",
+ "attribute-updates": "به روز رساني هاي ويژگي ",
+ "add-attribute-update": "افزودن به روز رساني ويژگي ",
+ "server-side-rpc": "سَمت سِروِر RPC",
+ "add-server-side-rpc-request": "سَمت سِروِر RPC افزودن درخواست",
+ "device-name-filter": "فيلتر نام دستگاه",
+ "attribute-filter": "فيلتر ويژگي ",
+ "method-filter": "فيلتر روش",
+ "request-topic-expression": "عبارت عنوان درخواست",
+ "response-timeout": "وقفه پاسخ در ميلي ثانيه",
+ "topic-expression": "بيان موضوع",
+ "client-scope": "حوزه مشتري",
+ "add-device": "افزودن دستگاه",
+ "opc-server": "سِروِرها",
+ "opc-add-server": "افزودن سِروِر",
+ "opc-add-server-prompt": "لطفا سِروِر را اضافه کنيد",
+ "opc-application-name": "نام برنامه کاربردي",
+ "opc-application-uri": "برنامه کاربردي URI",
+ "opc-scan-period-in-seconds": "دوره پويش در ثانيه",
+ "opc-security": "امنيت",
+ "opc-identity": "هويت",
+ "opc-keystore": "کي استور",
+ "opc-type": "نوع",
+ "opc-keystore-type": "نوع",
+ "opc-keystore-location": "* موقعيت مکاني",
+ "opc-keystore-password": "رمز عبور",
+ "opc-keystore-alias": "نام مستعار",
+ "opc-keystore-key-password": "کليد رمز عبور",
+ "opc-device-node-pattern": "الگوي گره دستگاه",
+ "opc-device-name-pattern": "الگوي نام دستگاه",
+ "modbus-server": "سِروِرها/جايگزين آماده به کار",
+ "modbus-add-server": "افزودن سِروِر/ جايگزين آماده به کار ",
+ "modbus-add-server-prompt": "لطفا سِروِرها/جايگزين آماده به کار را اضافه کنيد",
+ "modbus-transport": "انتقال",
+ "modbus-port-name": "نام در گاه سريال",
+ "modbus-encoding": "رمز گذاري",
+ "modbus-parity": "توازن",
+ "modbus-baudrate": "نرخ علامت در ثانيه",
+ "modbus-databits": "بيت هاي داده",
+ "modbus-stopbits": "بيت هاي توقف",
+ "modbus-databits-range": ".بيت هاي داده بايد در بازه اي بين 7 تا 8 باشند",
+ "modbus-stopbits-range": ".بيت هاي توقف بايد در بازه اي بين 1 تا 2 باشند",
+ "modbus-unit-id": "واحد ID",
+ "modbus-unit-id-range": ".واحد بايد در بازه اي بين 1 تا 247 باشد ID",
+ "modbus-device-name": "نام دستگاه",
+ "modbus-poll-period": "(ms) دوره نمونه برداري",
+ "modbus-attributes-poll-period": "(ms) دوره نمونه برداري ويژگي ها",
+ "modbus-timeseries-poll-period": "(ms) دوره نمونه برداري سري هاي زماني",
+ "modbus-poll-period-range": ".دوره نمونه برداري بايد مقداري مثبت باشد",
+ "modbus-tag": "برچسب",
+ "modbus-function": "تابع",
+ "modbus-register-address": "ثبت نام نشاني",
+ "modbus-register-address-range": ".نشاني ثبت بايد در بازه اي بين 0 تا 65535 باشد",
+ "modbus-register-bit-index": "شاخص بيت",
+ "modbus-register-bit-index-range": ".شاخص بيت بايد در بازه اي بين 0 تا 15 باشد",
+ "modbus-register-count": "شمارش ثبت",
+ "modbus-register-count-range": ".شمارش ثبت بايد مقداري مثبت باشد",
+ "modbus-byte-order": "ترتيب بايت",
+
+ "sync": {
+ "status": "وضعيت",
+ "sync": "همگام",
+ "not-sync": "غير همگام",
+ "last-sync-time": "آخرين زمان همگام سازي",
+ "not-available": "خارج از دسترس"
+ },
+
+ "export-extensions-configuration": "صدور پيکربندي افزونه ها",
+ "import-extensions-configuration": "وارد کردن پيکربندي افزونه ها",
+ "import-extensions": "وارد کردن افزونه ها",
+ "import-extension": "وارد کردن افزونه",
+ "export-extension": "صدور افزونه",
+ "file": "پرونده افزونه ها",
+ "invalid-file-error": "پرونده افزونه نامعتبر است"
+ },
+ "fullscreen": {
+ "expand": "بسط به حالت تمام صفحه",
+ "exit": "خروج از حالت تمام صفحه",
+ "toggle": "تغيير حالت تمام صفحه",
+ "fullscreen": "حالت تمام صفحه"
+ },
+ "function": {
+ "function": "تابع"
+ },
+ "grid": {
+ "delete-item-title": "از حذف اين مورد مطمئنيد؟",
+ "delete-item-text": ".مراقب باشيد، پس از تأييد، اين مورد و تمامي داده هاي مربوطه غيرقابل بازيابي مي شوند",
+ "delete-items-title": "مطمئنيد؟ { count, plural, 1 {1 مورد} other {# مورد} } از حذف",
+ "delete-items-action-title": "{ count, plural, 1 {1 مورد} other {# مورد} } حذف",
+ "delete-items-text": ".مراقب باشيد، پس از تأييد، تمام مواردِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "add-item-text": "افزودن مورد جديد",
+ "no-items-text": "هيچ موردي يافت نشد",
+ "item-details": "جزئيات مورد",
+ "delete-item": "حذف مورد",
+ "delete-items": "حذف موارد",
+ "scroll-to-top": "پيمايش به بالا"
+ },
+ "help": {
+ "goto-help-page": "رفتن به صفحه کمک"
+ },
+ "home": {
+ "home": "خانه",
+ "profile": "پرونده شخصي",
+ "logout": "خروج",
+ "menu": "فهرست انتخاب",
+ "avatar": "آواتار",
+ "open-user-menu": "بازکردن فهرست انتخاب کاربر"
+ },
+ "import": {
+ "no-file": "هيچ پرونده اي انتخاب نشد",
+ "drop-file": ".آن را با موس کِشيده و رها کنيد، و يا روي آن کليک نماييد ،JSON جهت بارگذاري يک پرونده"
+ },
+ "item": {
+ "selected": "انتخاب شده"
+ },
+ "js-func": {
+ "no-return-error": "!تابع بايد مقدار را برگرداند",
+ "return-type-mismatch": "!را برگرداند '{{type}}' تابع بايد مقدار نوع",
+ "tidy": "مرتب"
+ },
+ "key-val": {
+ "key": "کليد",
+ "value": "مقدار",
+ "remove-entry": "حذف ورودي",
+ "add-entry": "افزودن ورودي",
+ "no-data": "هيچ ورودي وجود ندارد"
+ },
+ "layout": {
+ "layout": "طرح بندي",
+ "manage": "مديريت طرح بندي ها",
+ "settings": "تنظيمات طرح بندي",
+ "color": "رنگ",
+ "main": "اصلي",
+ "right": "راست",
+ "select": "انتخاب طرح بندي هدف"
+ },
+ "legend": {
+ "position": "محل فهرست علائم",
+ "show-max": "نمايش بيشترين مقدار",
+ "show-min": "نمايش کمترين مقدار",
+ "show-avg": "نمايش مقدار ميانگين",
+ "show-total": "نمايش مقدار مجموع",
+ "settings": "تنظيمات فهرست علائم",
+ "min": "کمترين",
+ "max": "بيشترين",
+ "avg": "ميانگين",
+ "total": "مجموع"
+ },
+ "login": {
+ "login": "ورود",
+ "request-password-reset": "درخواست بازنشاني رمز عبور",
+ "reset-password": "بازنشاني رمز عبور",
+ "create-password": "ايجاد رمز عبور",
+ "passwords-mismatch-error": "!رمزهاي عبور وارد شده بايد مشابه باشند",
+ "password-again": "رمز عبور دوباره",
+ "sign-in": "لطفا وارد شويد",
+ "username": "(نام کاربري (پست الکترونيک",
+ "remember-me": "مرا به خاطر داشته باش",
+ "forgot-password": "رمز عبور را فراموش کرده ايد؟",
+ "password-reset": "بازنشاني رمز عبور",
+ "new-password": "رمز عبور جديد",
+ "new-password-again": "رمز عبور جديد دوباره",
+ "password-link-sent-message": "!پيوند بازنشاني رمز عبور با موفقيت ارسال شد",
+ "email": "پست الکترونيک"
+ },
+ "position": {
+ "top": "بالا",
+ "bottom": "پايين",
+ "left": "چپ",
+ "right": "راست"
+ },
+ "profile": {
+ "profile": "پرونده شخصي",
+ "change-password": "تغيير رمز عبور",
+ "current-password": "رمز عبور فعلي"
+ },
+ "relation": {
+ "relations": "ارتباطات",
+ "direction": "جهت",
+ "search-direction": {
+ "FROM": "از",
+ "TO": "به"
+ },
+ "direction-type": {
+ "FROM": "از",
+ "TO": "به"
+ },
+ "from-relations": "ارتباطات خارج از محدوده",
+ "to-relations": "ارتباطات داخل محدوده",
+ "selected-relations": "انتخاب شدند { count, plural, 1 {1 ارتباط} other {ارتباط #} }",
+ "type": "نوع",
+ "to-entity-type": "به نوع موجودي",
+ "to-entity-name": "به نام موجودي",
+ "from-entity-type": "از نوع موجودي",
+ "from-entity-name": "از نام موجودي",
+ "to-entity": "به موجودي",
+ "from-entity": "از موجودي",
+ "delete": "حذف ارتباط",
+ "relation-type": "نوع ارتباط",
+ "relation-type-required": ".نوع ارتباط مورد نياز است",
+ "any-relation-type": "هر نوع",
+ "add": "افزودن ارتباط",
+ "edit": "ويرايش ارتباط",
+ "delete-to-relation-title": "مطمئنيد؟ '{{entityName}}' از حذف ارتباط با موجودي",
+ "delete-to-relation-text": ".غيرمرتبط با موجودي فعلي مي شود '{{entityName}}' مراقب باشيد، پس از تأييد، موجودي",
+ "delete-to-relations-title": "مطمئنيد؟ { count, plural, 1 {1 ارتباط} other {# ارتباط} } از حذف",
+ "delete-to-relations-text": ".مراقب باشيد، پس از تأييد، تمام روابطِ انتخاب شده حذف، و موجودي هاي مربوطه، غيرمرتبط با موجودي فعلي مي شوند",
+ "delete-from-relation-title": "مطمئنيد؟ '{{entityName}}' از حذف ارتباط از موجودي",
+ "delete-from-relation-text": ".مي شود '{{entityName}}' مراقب باشيد، پس از تأييد، موجودي فعلي، غيرمرتبط از جانب موجودي",
+ "delete-from-relations-title": "مطمئنيد؟ { count, plural, 1 {1 ارتباط} other {# ارتباط} } از حذف",
+ "delete-from-relations-text": ".مراقب باشيد، پس از تأييد، تمام روابطِ انتخاب شده حذف، و موجودي فعلي، غيرمرتبط از جانب موجودي هاي مربوطه مي شود",
+ "remove-relation-filter": "حذف فيلتر ارتباط",
+ "add-relation-filter": "افزودن فيلتر ارتباط",
+ "any-relation": "هر ارتباط",
+ "relation-filters": "فيلترهاي ارتباط",
+ "additional-info": "(JSON) اطلاعات تکميلي",
+ "invalid-additional-info": "اطلاعات تکميلي ممکن نيست JSON تجزيه"
+ },
+ "rulechain": {
+ "rulechain": "زنجيره قواعد",
+ "rulechains": "زنجيره هاي قواعد",
+ "root": "پايه",
+ "delete": "حذف زنجيره قواعد",
+ "name": "نام",
+ "name-required": ".نام مورد نياز است",
+ "description": "توصيف",
+ "add": "افزودن زنجيره قواعد",
+ "set-root": "ريشه زنجيره قواعد را ايجاد کنيد",
+ "set-root-rulechain-title": "به عنوان ريشه مطمئنيد؟ '{{ruleChainName}}' از قرار دادن زنجيره قواعد",
+ "set-root-rulechain-text": ".پس از تأييد، زنجيره قواعد، به عنوان ريشه تعيين شده و به تمام پيامهاي انتقالي رسيدگي مي کند",
+ "delete-rulechain-title": "مطمئنيد؟ '{{ruleChainName}}' از حذف زنجيره قواعد",
+ "delete-rulechain-text": ".مراقب باشيد، پس از تأييد، زنجيره قواعد و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "delete-rulechains-title": "مطمئنيد؟ { count, plural, 1 {1 زنجيره قواعد} other {# زنجيره قواعد} } از حذف",
+ "delete-rulechains-action-title": "{ count, plural, 1 {1 زنجيره قواعد} other {# زنجيره قواعد} } حذف",
+ "delete-rulechains-text": ".مراقب باشيد، پس از تأييد، تمام زنجيره هاي قواعدِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "add-rulechain-text": "افزودن زنجيره قواعد جديد",
+ "no-rulechains-text": "هيچ زنجيره قواعدي يافت نشد",
+ "rulechain-details": "جزئيات زنجيره قواعد",
+ "details": "جزئيات",
+ "events": "رويدادها",
+ "system": "سيستم",
+ "import": "وارد کردن زنجيره قواعد",
+ "export": "صدور زنجيره قواعد",
+ "export-failed-error": "{{error}} :صدور زنجيره قواعد ممکن نيست",
+ "create-new-rulechain": "ايجاد زنجيره قواعد جديد",
+ "rulechain-file": "پرونده زنجيره قواعد",
+ "invalid-rulechain-file-error": ".وارد کردن زنجيره قواعد ممکن نيست: ساختار داده زنجيره قواعد نامعتبر است",
+ "copyId": "زنجيره قواعد ID رونوشت",
+ "idCopiedMessage": "زنجيره قواعد در حافظه موقت رونوشت شد ID",
+ "select-rulechain": "انتخاب زنجيره قواعد",
+ "no-rulechains-matching": ".يافت نشد '{{entity}}' هيچ زنجيره قواعدي منطبق بر",
+ "rulechain-required": "زنجيره قواعد مورد نياز است",
+ "management": "مديريت قواعد",
+ "debug-mode": "حالت اشکال زدايي"
+ },
+ "rulenode": {
+ "details": "جزئيات",
+ "events": "رويدادها",
+ "search": "جستجوي گره ها",
+ "open-node-library": "باز کردن کتابخانه گره ها",
+ "add": "افزودن گره قواعد",
+ "name": "نام",
+ "name-required": ".نام مورد نياز است",
+ "type": "نوع",
+ "description": "توصيف",
+ "delete": "حذف گره قواعد",
+ "select-all-objects": "انتخاب تمام گره ها و اتصالات",
+ "deselect-all-objects": "لغو انتخاب تمام گره ها و اتصالات",
+ "delete-selected-objects": "حذف گره ها و اتصالاتِ انتخاب شده",
+ "delete-selected": "حذفِ انتخاب شده",
+ "select-all": "انتخاب همه",
+ "copy-selected": "رونوشتِ انتخاب شده",
+ "deselect-all": "لغو انتخاب همه",
+ "rulenode-details": "جزئيات گره قواعد",
+ "debug-mode": "حالت اشکال زدايي",
+ "configuration": "پيکربندي",
+ "link": "پيوند",
+ "link-details": "جزئيات پيوند گره قواعد",
+ "add-link": "افزودن پيوند",
+ "link-label": "برچسب پيوند",
+ "link-label-required": ".برچسب پيوند مورد نياز است",
+ "custom-link-label": "برچسب پيوند متداول",
+ "custom-link-label-required": ".برچسب پيوند متداول مورد نياز است",
+ "link-labels": "برچسب هاي پيوند",
+ "link-labels-required": ".برچسب هاي پيوند مورد نيازند",
+ "no-link-labels-found": "هيچ برچسب پيوندي يافت نشد",
+ "no-link-label-matching": ".پيدا نشد'{{label}}'",
+ "create-new-link-label": "!برچسب پيوند ايجاد کنيد",
+ "type-filter": "فيلتر",
+ "type-filter-details": "پيام هاي ورودي را با شرايط پيکربندي فيلتر کنيد",
+ "type-enrichment": "افزودن",
+ "type-enrichment-details": "افزودن اطلاعات تکميلي به فرا داده ي پيام",
+ "type-transformation": "تبديل",
+ "type-transformation-details": "تغيير بازده و فرا داده ي پيام",
+ "type-action": "اقدام",
+ "type-action-details": "انجام اقدام ويژه",
+ "type-external": "خارجي",
+ "type-external-details": "ارتباط متقابل با سيستم خارجي",
+ "type-rule-chain": "زنجيره قواعد",
+ "type-rule-chain-details": "ارسال پيامهاي وارده به زنجيره قواعدي مشخص",
+ "type-input": "ورودي",
+ "type-input-details": "ورودي منطقي زنجيره قواعد، پيامهاي ورودي را به گره قواعد مرتبط بعدي ارسال مي کند",
+ "type-unknown": "ناشناخته",
+ "type-unknown-details": "گره قواعدِ حل نشده",
+ "directive-is-not-loaded": ".در دسترس نيست '{{directiveName}}' دستورالعمل پيکربنديِ مشخص شده",
+ "ui-resources-load-error": ".پيکربندي UI عدم موفقيت در بارگذاري منابع",
+ "invalid-target-rulechain": "!حلّ زنجيره قواعد هدف ممکن نيست",
+ "test-script-function": "آزمايش تابع اسکريپت",
+ "message": "پيام",
+ "message-type": "نوع پيام",
+ "select-message-type": "انتخاب نوع پيام",
+ "message-type-required": "نوع پيام مورد نياز است",
+ "metadata": "فوق داده",
+ "metadata-required": ".ورودي هاي فرا داده نمي تواند خالي باشد",
+ "output": "خروجي",
+ "test": "آزمايش",
+ "help": "کمک"
+ },
+ "tenant": {
+ "tenant": "کاربر",
+ "tenants": "کاربران",
+ "management": "مديريت کاربران",
+ "add": "افزودن کاربر",
+ "admins": "سرپرستان",
+ "manage-tenant-admins": "مديريت مديران کاربر",
+ "delete": "حذف کاربر",
+ "add-tenant-text": "افزودن کاربر جديد",
+ "no-tenants-text": "هيچ کاربري يافت نشد",
+ "tenant-details": "جزئيات کاربر",
+ "delete-tenant-title": "مطمئنيد؟ '{{tenantTitle}}' از حذف کاربر",
+ "delete-tenant-text": ".مراقب باشيد، پس از تأييد، کاربر و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "delete-tenants-title": "مطمئنيد؟ { count, plural, 1 {1 کاربر} other {# کاربر} } از حذف",
+ "delete-tenants-action-title": "{ count, plural, 1 {1 کاربر} other {# کاربر} } حذف",
+ "delete-tenants-text": ".مراقب باشيد، پس از تأييد، تمام کاربران حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "title": "عنوان",
+ "title-required": ".عنوان مورد نياز است",
+ "description": "توصيف",
+ "details": "جزئيات",
+ "events": "رويدادها",
+ "copyId": "متصرّف ID رونوشت",
+ "idCopiedMessage": "کاربر در حافظه موقت رونوشت شد ID",
+ "select-tenant": "انتخاب کاربر",
+ "no-tenants-matching": ".يافت نشد '{{entity}}' هيچ کاربري منطبق بر",
+ "tenant-required": "کاربر مورد نياز است"
+ },
+ "timeinterval": {
+ "seconds-interval": "{ seconds, plural, 1 {1 ثانيه} other {ثانيه #} }",
+ "minutes-interval": "{ minutes, plural, 1 {1 دقيقه} other {دقيقه #} }",
+ "hours-interval": "{ hours, plural, 1 {1 ساعت} other {ساعت #} }",
+ "days-interval": "{ days, plural, 1 {1 روز} other {روز #} }",
+ "days": "روزها",
+ "hours": "ساعات",
+ "minutes": "دقايق",
+ "seconds": "ثانيه ها",
+ "advanced": "پيشرفته"
+ },
+ "timewindow": {
+ "days": "{ days, plural, 1 { روز } other {روز #} }",
+ "hours": "{ hours, plural, 0 { 1ساعت } 1 { ساعت } other {# ساعت } }",
+ "minutes": "{ minutes, plural, 0 { 1دقيقه } 1 { دقيقه } other {دقيقه # } }",
+ "seconds": "{ seconds, plural, 0 { 1ثانيه } 1 { ثانيه } other {ثانيه # } }",
+ "realtime": "بي درنگ",
+ "history": "تاريخچه",
+ "last-prefix": "آخرين",
+ "period": "{{ endTime }} تا {{ startTime }} از",
+ "edit": "ويرايش پنجره زماني",
+ "date-range": "بازه داده",
+ "last": "آخرين",
+ "time-period": "دوره زماني"
+ },
+ "user": {
+ "user": "کاربر",
+ "users": "کاربرها",
+ "customer-users": "کاربرهاي مشتري",
+ "tenant-admins": "مديران کاربر",
+ "sys-admin": "مدير سيستم",
+ "tenant-admin": "کاربر مدير",
+ "customer": "مشتري",
+ "anonymous": "بي نام",
+ "add": "افزودن کاربر",
+ "delete": "حذف کاربر",
+ "add-user-text": "افزودن کاربر جديد",
+ "no-users-text": "هيچ کاربري يافت نشد",
+ "user-details": "جزئيات کاربر",
+ "delete-user-title": "مطمئنيد؟ '{{userEmail}}' از حذف کاربر",
+ "delete-user-text": ".مراقب باشيد، پس از تأييد، کاربر و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "delete-users-title": "مطمئنيد؟ { count, plural, 1 {1 کاربر} other {# کاربر} } از حذف",
+ "delete-users-action-title": "{ count, plural, 1 {1 کاربر} other {کاربر #} } حذف",
+ "delete-users-text": ".مراقب باشيد، پس از تأييد، تمام کاربرهاي انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "activation-email-sent-message": "!پست الکترونيک فعال سازي با موفقيت ارسال شد",
+ "resend-activation": "ارسال مجدد فعال سازي",
+ "email": "پست الکترونيک",
+ "email-required": ".پست الکترونيک مورد نياز است",
+ "invalid-email-format": ".قالب نامعتبر پست الکترونيک",
+ "first-name": "نام",
+ "last-name": "نام خانوادگي",
+ "description": "توصيف",
+ "default-dashboard": "داشبورد پيش فرض",
+ "always-fullscreen": "همواره در حالت تمام صفحه",
+ "select-user": "انتخاب کاربر",
+ "no-users-matching": ".يافت نشد '{{entity}}' هيچ کاربري منطبق بر",
+ "user-required": "کاربر مورد نياز است",
+ "activation-method": "روش فعال سازي",
+ "display-activation-link": "نمايش پيوند فعال سازي",
+ "send-activation-mail": "ارسال پست الکترونيک فعال سازي",
+ "activation-link": "پيوند فعال سازي کاربر",
+ "activation-link-text": ": </a>استفاده کنيد <a href='{{activationLink}}' target='_blank'> جهت فعال سازي کاربر، از پيوند فعال سازي زير",
+ "copy-activation-link": "رونوشت پيوند فعال سازي",
+ "activation-link-copied-message": "پيوند فعال سازي کاربر در حافظه موقت رونوشت شد",
+ "details": "جزئيات",
+ "login-as-tenant-admin": "ورود به عنوان کاربر مدير",
+ "login-as-customer-user": "ورود به عنوان کاربر مشتري"
+ },
+ "value": {
+ "type": "نوع مقدار",
+ "string": "رشته",
+ "string-value": "مقدار رشته",
+ "integer": "عدد صحيح",
+ "integer-value": "مقدار عدد صحيح",
+ "invalid-integer-value": "عدد صحيح نامعتبر",
+ "double": "دو برابر",
+ "double-value": "مقدار دو برابر",
+ "boolean": "بولين",
+ "boolean-value": "مقدار بولين",
+ "false": "نادرست",
+ "true": "صحيح",
+ "long": "بلند"
+ },
+ "widget": {
+ "widget-library": "کتابخانه ويجت ها",
+ "widget-bundle": "بسته ويجت",
+ "select-widgets-bundle": "انتخاب بسته ويجت",
+ "management": "مديريت ويجت",
+ "editor": "ويرايشگر ويجت",
+ "widget-type-not-found": ".نوع ويجت حذف شد \n احتمالا مرتبط است<br>.مشکل بارگذاري پيکربندي ويجت",
+ "widget-type-load-error": ":ويجت، به علت خطاهاي زير، بارگذاري نشد",
+ "remove": "حذف ويجت",
+ "edit": "ويرايش ويجت",
+ "remove-widget-title": "مطمئنيد؟ '{{widgetTitle}}' از حذف ويجت",
+ "remove-widget-text": ".پس از تأييد، ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "timeseries": "سري هاي زماني",
+ "search-data": "جستجوي داده",
+ "no-data-found": "هيچ داده اي يافت نشد",
+ "latest-values": "آخرين مقادير",
+ "rpc": "ويجت کنترل",
+ "alarm": "ويجت هشدار",
+ "static": "ويجت ايستا",
+ "select-widget-type": "انتخاب نوع ويجت",
+ "missing-widget-title-error": "!عنوان ويجت بايد مشخص شود",
+ "widget-saved": "ويجت ذخيره شد",
+ "unable-to-save-widget-error": "!ذخيره سازي ويجت ممکن نيست! ويجت خطاهايي دارد",
+ "save": "ذخيره سازي ويجت",
+ "saveAs": "ذخيره سازي ويجت به عنوان",
+ "save-widget-type-as": "ذخيره سازي نوع ويجت به عنوان",
+ "save-widget-type-as-text": "لطفا عنوان ويجت جديد را وارد کنيد و/يا بسته ويجت هدف را انتخاب نماييد",
+ "toggle-fullscreen": "حالت تمام صفحه را تغيير دهيد",
+ "run": "اجراي ويجت",
+ "title": "عنوان ويجت",
+ "title-required": ".عنوان ويجت مورد نياز است",
+ "type": "نوع ويجت",
+ "resources": "منابع",
+ "resource-url": "JavaScript/CSS URL",
+ "remove-resource": "حذف منبع",
+ "add-resource": "افزودن منبع",
+ "html": "HTML",
+ "tidy": "مرتب",
+ "css": "CSS",
+ "settings-schema": "طرح تنظيمات",
+ "datakey-settings-schema": "طرح تنظيمات کليد داده",
+ "javascript": "Javascript",
+ "remove-widget-type-title": "مطمئنيد؟ '{{widgetName}}' از حذف ويجت نوع",
+ "remove-widget-type-text": ".پس از تأييد، نوع ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "remove-widget-type": "حذف نوع ويجت",
+ "add-widget-type": "افزودن نوع ويجت جديد",
+ "widget-type-load-failed-error": "!عدم موفقيت در بارگذاري نوع ويجت",
+ "widget-template-load-failed-error": "!عدم موفقيت در بارگذاري قالب ويجت",
+ "add": "افزودن ويجت",
+ "undo": "برگرداندن تغييرات ويجت",
+ "export": "صدور ويجت"
+ },
+ "widget-action": {
+ "header-button": "دکمه هدر ويجت",
+ "open-dashboard-state": "هدايت به وضعيت داشبورد جديد",
+ "update-dashboard-state": "به روز رساني وضعيت داشبورد فعلي",
+ "open-dashboard": "هدايت به داشبورد ديگر",
+ "custom": "اقدام متداول",
+ "target-dashboard-state": "وضعيت داشبورد هدف",
+ "target-dashboard-state-required": "وضعيت داشبورد هدف مورد نياز است",
+ "set-entity-from-widget": "تنظيم موجودي از ويجت",
+ "target-dashboard": "داشبورد هدف",
+ "open-right-layout": "(طرح داشبورد سمت راست را باز کنيد (نماي تلفن همراه"
+ },
+ "widgets-bundle": {
+ "current": "بسته فعلي",
+ "widgets-bundles": "بسته هاي ويجت",
+ "add": "افزودن بسته ويجت",
+ "delete": "حذف بسته ويجت",
+ "title": "عنوان",
+ "title-required": ".عنوان مورد نياز است",
+ "add-widgets-bundle-text": "افزودن بسته ويجت جديد",
+ "no-widgets-bundles-text": "هيچ بسته ويجتي يافت نشد",
+ "empty": "بسته ويجت خالي است",
+ "details": "جزئيات",
+ "widgets-bundle-details": "جزئيات بسته ويجت",
+ "delete-widgets-bundle-title": "مطمئنيد؟ '{{widgetsBundleTitle}}' از حذف بسته ويجت",
+ "delete-widgets-bundle-text": ".مراقب باشيد، پس از تأييد، بسته ويجت و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "delete-widgets-bundles-title": "مطمئنيد؟ { count, plural, 1 {1 بسته ويجت} other {# بسته ويجت} } از حذف",
+ "delete-widgets-bundles-action-title": "{ count, plural, 1 {1 بسته ويجت} other {# بسته ويجت} } حذف",
+ "delete-widgets-bundles-text": ".مراقب باشيد، پس از تأييد، تمام بسته هاي ويجتِ انتخاب شده حذف، و تمامي داده هاي مربوطه غير قابل بازيابي مي شوند",
+ "no-widgets-bundles-matching": "يافت نشد '{{widgetsBundle}}' هيچ بسته ويجتي منطبق بر",
+ "widgets-bundle-required": ".بسته ويجت مورد نياز است",
+ "system": "سيستم",
+ "import": "وارد کردن بسته ويجت",
+ "export": "صدور بسته ويجت",
+ "export-failed-error": "{{error}} :صدور بسته ويجت ممکن نيست",
+ "create-new-widgets-bundle": "ايجاد بسته ويجت جديد",
+ "widgets-bundle-file": "پرونده بسته ويجت",
+ "invalid-widgets-bundle-file-error": ".وارد کردن بسته ويجت ممکن نيست: ساختار داده بسته ويجت نامعتبر است"
+ },
+ "widget-config": {
+ "data": "داده",
+ "settings": "تنظيمات",
+ "advanced": "پيشرفته",
+ "title": "عنوان",
+ "general-settings": "تنظيمات عمومي",
+ "display-title": "نمايش عنوان",
+ "drop-shadow": "سايه افتادن",
+ "enable-fullscreen": "فعال سازي حالت تمام صفحه",
+ "background-color": "رنگ پس زمينه",
+ "text-color": "رنگ متن",
+ "padding": "حاشيه داخلي",
+ "margin": "حاشيه",
+ "widget-style": "سبک ويجت",
+ "title-style": "سبک عنوان",
+ "mobile-mode-settings": "تنظيمات حالت تلفن همراه",
+ "order": "ترتيب",
+ "height": "ارتفاع",
+ "units": "کارکتر خاص براي نمايش بعد از مقدار تعين شده",
+ "decimals": "تعداد ارقام بعد از مميّز شناور",
+ "timewindow": "پنجره زمان",
+ "use-dashboard-timewindow": "استفاده از پنجره زمان داشبورد",
+ "display-legend": "نمايش فهرست علائم",
+ "datasources": "منابع داده",
+ "maximum-datasources": "{ count, plural, 1 {.1 منبع داده مجاز است} other {# منبع داده مجازند.} } بيشترين",
+ "datasource-type": "نوع",
+ "datasource-parameters": "پارامترها",
+ "remove-datasource": "حذف منبع داده",
+ "add-datasource": "افزودن منبع داده",
+ "target-device": "دستگاه هدف",
+ "alarm-source": "منشأ هشدار",
+ "actions": "اقدامات",
+ "action": "اقدام",
+ "add-action": "افزودن اقدام",
+ "search-actions": "جستجوي اقدامات",
+ "action-source": "منشأ اقدام",
+ "action-source-required": ".منشأ اقدام مورد نياز است",
+ "action-name": "نام",
+ "action-name-required": ".نام اقدام مورد نياز است",
+ "action-name-not-unique": ".در حيطه يک منشأ اقدام، نام اقدام بايد منحصر بفرد باشد<br/>.در حال حاضر اقدامي ديگر با نام مشابه موجود است",
+ "action-icon": "شمايل",
+ "action-type": "نوع",
+ "action-type-required": ".نوع اقدام مورد نياز است",
+ "edit-action": "ويرايش اقدام",
+ "delete-action": "حذف اقدام",
+ "delete-action-title": "حذف اقدام ويجت",
+ "delete-action-text": "مطمئنيد؟ '{{actionName}}' از حذف اقدام ويجت با نام"
+ },
+ "widget-type": {
+ "import": "وارد کردن نوع ويجت",
+ "export": "صدور نوع ويجت",
+ "export-failed-error": "{{error}} :صدور نوع ويجت ممکن نيست",
+ "create-new-widget-type": "ايجاد نوع جديد ويجت",
+ "widget-type-file": "پرونده نوع ويجت",
+ "invalid-widget-type-file-error": ".وارد کردن نوع ويجت ممکن نيست: ساختار داده نوع ويجت نامعتبر است"
+ },
+ "icon": {
+ "icon": "آيکون",
+ "select-icon": "انتخاب آيکون",
+ "material-icons": "آيکونهاي اجسام",
+ "show-all": "نمايش تمام آيکونها"
+ },
+ "custom": {
+ "widget-action": {
+ "action-cell-button": "دکمه سلول عملياتي",
+ "row-click": "در رديف کليک کنيد",
+ "marker-click": "روي نشانگر کليک کنيد",
+ "tooltip-tag-action": "اقدام برچسب راهنماي ابزار"
+ }
+ },
+ "language": {
+ "language": "زبان",
+ "locales": {
+ "de_DE": "آلمانی",
+ "fr_FR": "فرانسوي",
+ "zh_CN": "چيني",
+ "en_US": "انگليسي",
+ "it_IT": "ايتاليايي",
+ "ko_KR": "کره اي",
+ "ru_RU": "روسي",
+ "es_ES": "اسپانيولي",
+ "ja_JA": "ژاپني",
+ "tr_TR": "ترکي",
+ "fa_IR": "فارسي"
+ }
+ }
+}
diff --git a/ui/src/app/locale/locale.constant-fr_FR.json b/ui/src/app/locale/locale.constant-fr_FR.json
index 5b4edd9..651b695 100644
--- a/ui/src/app/locale/locale.constant-fr_FR.json
+++ b/ui/src/app/locale/locale.constant-fr_FR.json
@@ -1011,7 +1011,9 @@
"ko_KR": "Coréen",
"ru_RU": "Russe",
"zh_CN": "Chinois",
- "tr_TR": "Turc"
+ "ja_JA": "Japonaise",
+ "tr_TR": "Turc",
+ "fa_IR": "Persane"
}
},
"layout": {
diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json
index 4c73548..1d3a84f 100644
--- a/ui/src/app/locale/locale.constant-it_IT.json
+++ b/ui/src/app/locale/locale.constant-it_IT.json
@@ -1441,7 +1441,8 @@
"ru_RU": "Russo",
"es_ES": "Spagnolo",
"ja_JA": "Giapponese",
- "tr_TR": "Turco"
+ "tr_TR": "Turco",
+ "fa_IR": "Persiana"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-ja_JA.json b/ui/src/app/locale/locale.constant-ja_JA.json
index fea4ab3..ce4d849 100644
--- a/ui/src/app/locale/locale.constant-ja_JA.json
+++ b/ui/src/app/locale/locale.constant-ja_JA.json
@@ -1450,6 +1450,7 @@
"language": "言語",
"locales": {
"de_DE": "ドイツ語",
+ "fr_FR": "フランス語",
"en_US": "英語",
"ko_KR": "韓国語",
"it_IT": "イタリアの",
@@ -1457,7 +1458,8 @@
"ru_RU": "ロシア",
"es_ES": "スペイン語",
"ja_JA": "日本語",
- "tr_TR": "トルコ語"
+ "tr_TR": "トルコ語",
+ "fa_IR": "ペルシャ語"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-ko_KR.json b/ui/src/app/locale/locale.constant-ko_KR.json
index 4ddd9b5..57aef0e 100644
--- a/ui/src/app/locale/locale.constant-ko_KR.json
+++ b/ui/src/app/locale/locale.constant-ko_KR.json
@@ -1334,7 +1334,8 @@
"es_ES": "스페인어",
"it_IT": "이탈리아 사람",
"ja_JA": "일본어",
- "tr_TR": "터키어"
+ "tr_TR": "터키어",
+ "fa_IR": "페르시아 인"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json
index c087649..d31d866 100644
--- a/ui/src/app/locale/locale.constant-ru_RU.json
+++ b/ui/src/app/locale/locale.constant-ru_RU.json
@@ -1573,7 +1573,8 @@
"ru_RU": "Русский",
"tr_TR": "Турецкий",
"fr_FR": "Французский",
- "ja_JA": "Японский"
+ "ja_JA": "Японский",
+ "fa_IR": "Персидский"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-tr_TR.json b/ui/src/app/locale/locale.constant-tr_TR.json
index 3f2e37e..b96eaaf 100644
--- a/ui/src/app/locale/locale.constant-tr_TR.json
+++ b/ui/src/app/locale/locale.constant-tr_TR.json
@@ -1540,7 +1540,8 @@
"ru_RU": "Rusça",
"es_ES": "İspanyol",
"ja_JA": "Japonca",
- "tr_TR": "Türkçe"
+ "tr_TR": "Türkçe",
+ "fa_IR": "Farsça"
}
}
}
\ No newline at end of file
diff --git a/ui/src/app/locale/locale.constant-zh_CN.json b/ui/src/app/locale/locale.constant-zh_CN.json
index bacddc4..141c613 100644
--- a/ui/src/app/locale/locale.constant-zh_CN.json
+++ b/ui/src/app/locale/locale.constant-zh_CN.json
@@ -1444,7 +1444,8 @@
"es_ES": "西班牙语",
"it_IT": "意大利",
"ja_JA": "日本",
- "tr_TR": "土耳其"
+ "tr_TR": "土耳其",
+ "fa_IR": "波斯语"
}
}
}
\ No newline at end of file
ui/webpack.config.dev.js 28(+24 -4)
diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js
index cfff811..dec2c77 100644
--- a/ui/webpack.config.dev.js
+++ b/ui/webpack.config.dev.js
@@ -18,13 +18,15 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
-const StyleLintPlugin = require('stylelint-webpack-plugin')
+const StyleLintPlugin = require('stylelint-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
const dirTree = require('directory-tree');
const jsonminify = require("jsonminify");
+const HappyPack = require('happypack');
+
const PUBLIC_RESOURCE_PATH = '/';
var langs = [];
@@ -34,6 +36,9 @@ dirTree('./src/app/locale/', {extensions:/\.json$/}, (item) => {
langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5));
});
+
+var happyThreadPool = HappyPack.ThreadPool({ size: 3 });
+
/* devtool: 'cheap-module-eval-source-map', */
module.exports = {
@@ -93,6 +98,21 @@ module.exports = {
PUBLIC_PATH: JSON.stringify(PUBLIC_RESOURCE_PATH),
SUPPORTED_LANGS: JSON.stringify(langs)
}),
+ new HappyPack({
+ threadPool: happyThreadPool,
+ id: 'cached-babel',
+ loaders: ["babel-loader?cacheDirectory=true"]
+ }),
+ new HappyPack({
+ threadPool: happyThreadPool,
+ id: 'eslint',
+ loaders: ["eslint-loader?{parser: 'babel-eslint'}"]
+ }),
+ new HappyPack({
+ threadPool: happyThreadPool,
+ id: 'ng-annotate-and-cached-babel-loader',
+ loaders: ['ng-annotate', 'babel-loader?cacheDirectory=true']
+ })
],
node: {
tls: "empty",
@@ -102,19 +122,19 @@ module.exports = {
loaders: [
{
test: /\.jsx$/,
- loader: 'babel',
+ loader: 'happypack/loader?id=cached-babel',
exclude: /node_modules/,
include: __dirname,
},
{
test: /\.js$/,
- loaders: ['ng-annotate', 'babel'],
+ loaders: ['happypack/loader?id=ng-annotate-and-cached-babel-loader'],
exclude: /node_modules/,
include: __dirname,
},
{
test: /\.js$/,
- loader: "eslint-loader?{parser: 'babel-eslint'}",
+ loader: 'happypack/loader?id=eslint',
exclude: /node_modules|vendor/,
include: __dirname,
},
ui/webpack.config.prod.js 30(+27 -3)
diff --git a/ui/webpack.config.prod.js b/ui/webpack.config.prod.js
index a442590..c0809fe 100644
--- a/ui/webpack.config.prod.js
+++ b/ui/webpack.config.prod.js
@@ -23,6 +23,8 @@ const webpack = require('webpack');
const path = require('path');
const dirTree = require('directory-tree');
const jsonminify = require("jsonminify");
+const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
+const HappyPack = require('happypack');
const PUBLIC_RESOURCE_PATH = '/static/';
@@ -33,6 +35,8 @@ dirTree('./src/app/locale/', {extensions:/\.json$/}, (item) => {
langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5));
});
+var happyThreadPool = HappyPack.ThreadPool({ size: 3 });
+
module.exports = {
devtool: 'source-map',
entry: [
@@ -95,6 +99,25 @@ module.exports = {
test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2|\.eot$\.json$/,
threshold: 10240,
minRatio: 0.8
+ }),
+ new UglifyJsPlugin({
+ cache: true,
+ parallel: true
+ }),
+ new HappyPack({
+ threadPool: happyThreadPool,
+ id: 'cached-babel',
+ loaders: ["babel-loader?cacheDirectory=true"]
+ }),
+ new HappyPack({
+ threadPool: happyThreadPool,
+ id: 'eslint',
+ loaders: ["eslint-loader?{parser: 'babel-eslint'}"]
+ }),
+ new HappyPack({
+ threadPool: happyThreadPool,
+ id: 'ng-annotate-and-cached-babel-loader',
+ loaders: ['ng-annotate', 'babel-loader?cacheDirectory=true']
})
],
node: {
@@ -105,19 +128,20 @@ module.exports = {
loaders: [
{
test: /\.jsx$/,
- loader: 'babel',
+ loader: 'happypack/loader?id=cached-babel',
exclude: /node_modules/,
include: __dirname,
},
{
test: /\.js$/,
- loaders: ['ng-annotate', 'babel'],
+ loaders: ['happypack/loader?id=ng-annotate-and-cached-babel-loader'],
exclude: /node_modules/,
include: __dirname,
},
{
test: /\.js$/,
- loader: "eslint-loader?{parser: 'babel-eslint'}",
+ loaders: ['happypack/loader?id=eslint'],
+ // loader: "eslint-loader?{parser: 'babel-eslint'}",
exclude: /node_modules|vendor/,
include: __dirname,
},