Skip to content

Commit

Permalink
Merge branch 'main' into native-editor-links-images
Browse files Browse the repository at this point in the history
tonisevener authored Jan 8, 2024
2 parents 96f80ca + 9ba2a3d commit 48a1c7b
Showing 13 changed files with 449 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -16,19 +16,63 @@ extension WKSourceEditorFormatter {

func toggleFormatting(startingFormattingString: String, endingFormattingString: String, action: WKSourceEditorFormatterButtonAction, in textView: UITextView) {

switch action {
case .remove:
expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)

if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) {
removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
if textView.selectedRange.length == 0 {
switch action {
case .remove:
expandSelectedRangeUpToNearestFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)

if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) {
removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
}
case .add:
if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) {
removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
} else {
addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
}
}
case .add:
if textView.selectedRange.length == 0 &&
selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) {
removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
} else {
addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
} else {

switch action {
case .remove:

if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView) {
removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
} else {

// Note the flipped formatting string params.
// For example, this takes selected 'text' from:
// Testing <s>Strikethrough text here</s>
// To:
// Testing <s>Strikethrough </s>text<s> here</s>
// We have to add formatters in reverse order to remove formatting from 'text'

addStringFormattingCharacters(startingFormattingString: endingFormattingString, endingFormattingString: startingFormattingString, in: textView)
}

case .add:

// Note: gross workaround to prevent italics misfire from continuing below
if startingFormattingString == "''" && endingFormattingString == "''" {
if selectedRangeIsSurroundedByFormattingString(formattingString: "''", in: textView) &&
selectedRangeIsSurroundedByFormattingString(formattingString: "'''", in: textView) {
addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
return
}
}

// Note the flipped formatting string params.
// For example, this takes selected 'text' from:
// Testing <s>Strikethrough </s>text<s> here</s>
// To:
// Testing <s>Strikethrough text here</s>
// We have to check and remove formatters in reverse order to add formatting to 'text'

if selectedRangeIsSurroundedByFormattingStrings(startingFormattingString: endingFormattingString, endingFormattingString: startingFormattingString, in: textView) {
removeSurroundingFormattingStringsFromSelectedRange(startingFormattingString: endingFormattingString, endingFormattingString: startingFormattingString, in: textView)
} else {
addStringFormattingCharacters(startingFormattingString: startingFormattingString, endingFormattingString: endingFormattingString, in: textView)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -278,70 +278,56 @@ - (void)updateFonts:(WKSourceEditorFonts *)fonts inAttributedString:(NSMutableAt
#pragma mark - Public

- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isBoldInRange:(NSRange)range {
__block BOOL isBold = NO;
if (range.length == 0) {

if (attributedString.length > range.location) {
NSDictionary<NSAttributedStringKey,id> *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil];

if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) {
isBold = YES;
} else {
// Edge case, check previous character if we are up against a closing bold or italic
if (attrs[WKSourceEditorCustomKeyColorOrange]) {
attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil];
if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) {
isBold = YES;
}
}
}
}

} else {
[attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) {
if ((attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontBold] != nil) &&
(loopRange.location == range.location && loopRange.length == range.length)) {
isBold = YES;
stop = YES;
}
}];
}


return isBold;
return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontBold];
}

- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isItalicsInRange:(NSRange)range {
__block BOOL isItalics = NO;
return [self attributedString:attributedString isFormattedInRange:range formattingKey:WKSourceEditorCustomKeyFontItalics];
}

#pragma mark - Private

- (BOOL)attributedString:(NSMutableAttributedString *)attributedString isFormattedInRange:(NSRange)range formattingKey: (NSString *)formattingKey {
__block BOOL isFormatted = NO;

if (range.length == 0) {

if (attributedString.length > range.location) {
NSDictionary<NSAttributedStringKey,id> *attrs = [attributedString attributesAtIndex:range.location effectiveRange:nil];

if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) {
isItalics = YES;
isFormatted = YES;
} else {
// Edge case, check previous character if we are up against a closing bold or italic
if (attrs[WKSourceEditorCustomKeyColorOrange]) {
attrs = [attributedString attributesAtIndex:range.location - 1 effectiveRange:nil];
if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) {
isItalics = YES;
if (attrs[WKSourceEditorCustomKeyFontBold] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) {
isFormatted = YES;
}
}
}
}

} else {

__block NSRange unionRange = NSMakeRange(NSNotFound, 0);
[attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) {
if ((attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[WKSourceEditorCustomKeyFontItalics] != nil) &&
(loopRange.location == range.location && loopRange.length == range.length)) {
isItalics = YES;
if (attrs[WKSourceEditorCustomKeyFontBoldItalics] != nil || attrs[formattingKey] != nil) {
if (unionRange.location == NSNotFound) {
unionRange = loopRange;
} else {
unionRange = NSUnionRange(unionRange, loopRange);
}
stop = YES;
}
}];

if (NSEqualRanges(unionRange, range)) {
isFormatted = YES;
}
}

return isItalics;
return isFormatted;
}

@end
37 changes: 24 additions & 13 deletions Components/Sources/ComponentsObjC/WKSourceEditorFormatterTemplate.m
Original file line number Diff line number Diff line change
@@ -33,8 +33,8 @@ - (instancetype)initWithColors:(WKSourceEditorColors *)colors fonts:(WKSourceEdi
WKSourceEditorCustomKeyVerticalTemplate: [NSNumber numberWithBool:YES]
};

_horizontalTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"\\{{2}[^\\{\\}\\n]*\\}{2}" options:0 error:nil];
_verticalStartTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^\\{{2}[^\\{\\}\\n]*$" options:NSRegularExpressionAnchorsMatchLines error:nil];
_horizontalTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"\\{{2}[^\\{\\}\\n]*(?:\\{{2}[^\\{\\}\\n]*\\}{2})*[^\\{\\}\\n]*\\}{2}" options:0 error:nil];
_verticalStartTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^(?:.*)(\\{{2}[^\\{\\}\\n]*)$" options:NSRegularExpressionAnchorsMatchLines error:nil];
_verticalParameterTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^\\s*\\|.*$" options:NSRegularExpressionAnchorsMatchLines error:nil];
_verticalEndTemplateRegex = [[NSRegularExpression alloc] initWithPattern:@"^([^\\{\\}\n]*\\}{2})(?:.)*$" options:NSRegularExpressionAnchorsMatchLines error:nil];
}
@@ -64,11 +64,12 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri
options:0
range:range
usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) {
NSRange matchRange = [result rangeAtIndex:0];
NSRange fullMatch = [result rangeAtIndex:0];
NSRange openingTemplateRange = [result rangeAtIndex:1];

if (matchRange.location != NSNotFound) {
[attributedString addAttributes:self.verticalTemplateAttributes range:matchRange];
}
if (fullMatch.location != NSNotFound && openingTemplateRange.location != NSNotFound) {
[attributedString addAttributes:self.verticalTemplateAttributes range:openingTemplateRange];
}
}];

[self.verticalParameterTemplateRegex enumerateMatchesInString:attributedString.string
@@ -86,12 +87,14 @@ - (void)addSyntaxHighlightingToAttributedString:(nonnull NSMutableAttributedStri
options:0
range:range
usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) {

NSRange fullMatch = [result rangeAtIndex:0];
NSRange closingTemplateRange = [result rangeAtIndex:1];

if (fullMatch.location != NSNotFound && closingTemplateRange.location != NSNotFound) {
[attributedString addAttributes:self.verticalTemplateAttributes range:closingTemplateRange];
}
if (fullMatch.location != NSNotFound && closingTemplateRange.location != NSNotFound) {
[attributedString addAttributes:self.verticalTemplateAttributes range:closingTemplateRange];
}

}];
}

@@ -149,13 +152,21 @@ - (BOOL)attributedString:(NSMutableAttributedString *)attributedString isHorizon
}

} else {
__block NSRange unionRange = NSMakeRange(NSNotFound, 0);
[attributedString enumerateAttributesInRange:range options:nil usingBlock:^(NSDictionary<NSAttributedStringKey,id> * _Nonnull attrs, NSRange loopRange, BOOL * _Nonnull stop) {
if ((attrs[WKSourceEditorCustomKeyHorizontalTemplate] != nil) &&
(loopRange.location == range.location && loopRange.length == range.length)) {
isTemplate = YES;
stop = YES;
if (attrs[WKSourceEditorCustomKeyHorizontalTemplate] != nil) {
if (unionRange.location == NSNotFound) {
unionRange = loopRange;
} else {
unionRange = NSUnionRange(unionRange, loopRange);
}
stop = YES;
}
}];

if (NSEqualRanges(unionRange, range)) {
isTemplate = YES;
}
}

return isTemplate;
Original file line number Diff line number Diff line change
@@ -42,15 +42,15 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase {
XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four")
}

func testSingleBoldRemove() throws {
func testCursorBoldRemove() throws {
let text = "One '''Two''' Three Four"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 8, length: 0) // Just a cursor inside Two
mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four")
}

func testSingleItalicsRemove() throws {
func testCursorItalicsRemove() throws {
let text = "One Two '''Three''' Four"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 14, length: 0)
@@ -77,7 +77,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase {
XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four")
}

func testSingleBoldInsertAndRemove() throws {
func testCursorBoldInsertAndRemove() throws {
let text = "One Two Three Four"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 4, length: 0) // Just a cursor before Two
@@ -87,7 +87,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase {
XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four")
}

func testSingleItalicsInsertAndRemove() throws {
func testCursorItalicsInsertAndRemove() throws {
let text = "One Two Three Four"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 4, length: 0) // Just a cursor before Two
@@ -97,6 +97,46 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase {
XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four")
}

func testBoldInnerRemoveAndInsert() throws {
let text = "One '''Two Three Four''' Five"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 11, length: 5) // Selected Three
mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One '''Two '''Three''' Four''' Five")
mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One '''Two Three Four''' Five")
}

func testItalicsInnerRemoveAndInsert() throws {
let text = "One ''Two Three Four'' Five"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 10, length: 5) // Selected Three
mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One ''Two ''Three'' Four'' Five")
mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One ''Two Three Four'' Five")
}

func testBoldItalicsInnerRemoveBoldAndInsert() throws {
let text = "One '''''Two Three Four''''' Five"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 13, length: 5) // Selected Three
mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two '''Three''' Four''''' Five")
mediator.boldItalicsFormatter?.toggleBoldFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two Three Four''''' Five")
}

func testBoldItalicsInnerRemoveItalicsAndInsert() throws {
let text = "One '''''Two Three Four''''' Five"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 13, length: 5) // Selected Three
mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two ''Three'' Four''''' Five")
mediator.boldItalicsFormatter?.toggleItalicsFormatting(action: .remove, in: mediator.textView)
XCTAssertEqual(mediator.textView.attributedText.string, "One '''''Two Three Four''''' Five")
}

func testTemplateInsert() throws {
let text = "One Two Three Four"
mediator.textView.attributedText = NSAttributedString(string: text)
@@ -113,7 +153,7 @@ final class WKSourceEditorFormatterButtonActionTests: XCTestCase {
XCTAssertEqual(mediator.textView.attributedText.string, "One Two Three Four")
}

func testSingleTemplateInsertAndRemove() throws {
func testCursorTemplateInsertAndRemove() throws {
let text = "One Two Three Four"
mediator.textView.attributedText = NSAttributedString(string: text)
mediator.textView.selectedRange = NSRange(location: 4, length: 0) // Just a cursor before Two
Original file line number Diff line number Diff line change
@@ -636,7 +636,43 @@ final class WKSourceEditorFormatterTests: XCTestCase {
XCTAssertEqual(refClosingAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect ref formatting")
}

func testVerticalStartTemplate() {
func testHorizontalNestedTemplate() {
let string = "Ford Island ({{lang-haw|Poka {{okina}}Ailana}}) is an"
let mutAttributedString = NSMutableAttributedString(string: string)

for formatter in formatters {
formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count))
}

var base1Range = NSRange(location: 0, length: 0)
let base1Attributes = mutAttributedString.attributes(at: 0, effectiveRange: &base1Range)

var templateRange = NSRange(location: 0, length: 0)
let templateAttributes = mutAttributedString.attributes(at: 13, effectiveRange: &templateRange)

var base2Range = NSRange(location: 0, length: 0)
let base2Attributes = mutAttributedString.attributes(at: 46, effectiveRange: &base2Range)

// "Ford Island ("
XCTAssertEqual(base1Range.location, 0, "Incorrect base formatting")
XCTAssertEqual(base1Range.length, 13, "Incorrect base formatting")
XCTAssertEqual(base1Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting")
XCTAssertEqual(base1Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting")

// "{{lang-haw|Poka {{okina}}Ailana}}"
XCTAssertEqual(templateRange.location, 13, "Incorrect template formatting")
XCTAssertEqual(templateRange.length, 33, "Incorrect template formatting")
XCTAssertEqual(templateAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect template formatting")
XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect template formatting")

// ") is an"
XCTAssertEqual(base2Range.location, 46, "Incorrect base formatting")
XCTAssertEqual(base2Range.length, 7, "Incorrect base formatting")
XCTAssertEqual(base2Attributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting")
XCTAssertEqual(base2Attributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting")
}

func testVerticalStartTemplate1() {
let string = "{{Infobox officeholder"
let mutAttributedString = NSMutableAttributedString(string: string)

@@ -654,6 +690,33 @@ final class WKSourceEditorFormatterTests: XCTestCase {
XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect template formatting")
}

func testVerticalStartTemplate2() {
let string = "ending of previous sentence. {{cite web"
let mutAttributedString = NSMutableAttributedString(string: string)

for formatter in formatters {
formatter.addSyntaxHighlighting(to: mutAttributedString, in: NSRange(location: 0, length: string.count))
}

var baseRange = NSRange(location: 0, length: 0)
let baseAttributes = mutAttributedString.attributes(at: 0, effectiveRange: &baseRange)

var templateRange = NSRange(location: 0, length: 0)
let templateAttributes = mutAttributedString.attributes(at: 29, effectiveRange: &templateRange)

// "ending of previous sentence. "
XCTAssertEqual(baseRange.location, 0, "Incorrect base formatting")
XCTAssertEqual(baseRange.length, 29, "Incorrect base formatting")
XCTAssertEqual(baseAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect base formatting")
XCTAssertEqual(baseAttributes[.foregroundColor] as! UIColor, colors.baseForegroundColor, "Incorrect base formatting")

// "ending of previous sentence. "
XCTAssertEqual(templateRange.location, 29, "Incorrect template formatting")
XCTAssertEqual(templateRange.length, 10, "Incorrect template formatting")
XCTAssertEqual(templateAttributes[.font] as! UIFont, fonts.baseFont, "Incorrect template formatting")
XCTAssertEqual(templateAttributes[.foregroundColor] as! UIColor, colors.purpleForegroundColor, "Incorrect base formatting")
}

func testVerticalParameterTemplate() {
let string = "| genus = Felis"
let mutAttributedString = NSMutableAttributedString(string: string)
Original file line number Diff line number Diff line change
@@ -61,6 +61,25 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase {
XCTAssertFalse(selectionStates9.isItalics)
}

func testSelectionSpanningNonFormattedState1() throws {
let text = "Testing '''bold with {{template}}''' selection that spans nonbold."
mediator.textView.attributedText = NSAttributedString(string: text)

// "bold with {{template}}"
let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 11, length: 22))
XCTAssertTrue(selectionStates.isBold)
XCTAssertFalse(selectionStates.isHorizontalTemplate)
}

func testSelectionSpanningNonFormattedState2() throws {
let text = "Testing {{template | '''bold'''}} selection that spans nonbold."
mediator.textView.attributedText = NSAttributedString(string: text)

// "template | '''bold'''"
let selectionStates1 = mediator.selectionState(selectedDocumentRange: NSRange(location: 10, length: 21))
XCTAssertFalse(selectionStates1.isBold)
XCTAssertTrue(selectionStates1.isHorizontalTemplate)
}
func testClosingBoldSelectionStateCursor() throws {
let text = "One '''Two''' Three"
mediator.textView.attributedText = NSAttributedString(string: text)
@@ -138,6 +157,15 @@ final class WKSourceEditorTextFrameworkMediatorTests: XCTestCase {
XCTAssertFalse(selectionStates1.isHorizontalTemplate)
}

func testHorizontalTemplateButtonSelectionStateFormattedRange() throws {
let text = "Testing inner formatted {{cite web | url=https://en.wikipedia.org | title = The '''Free''' Encyclopedia}} template example."
mediator.textView.attributedText = NSAttributedString(string: text)

// "cite web | url=https://en.wikipedia.org | title = The '''Free''' Encyclopedia"
let selectionStates = mediator.selectionState(selectedDocumentRange: NSRange(location: 26, length: 77))
XCTAssertTrue(selectionStates.isHorizontalTemplate)
}

func testStrikethroughSelectionState() throws {
let text = "Testing <s>Strikethrough</s> Testing."
mediator.textView.attributedText = NSAttributedString(string: text)
60 changes: 60 additions & 0 deletions Wikipedia/Localizations/da.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -214,9 +214,20 @@
"diff-thanks-send-button-title" = "Send 'Tak'";
"diff-thanks-sent" = "Din 'Tak' blev sendt til $1";
"dim-images" = "Mørklæg billeder";
"edit-bold-accessibility-label" = "Tilføj fed formatering";
"edit-bold-remove-accessibility-label" = "Fjern fed formatering";
"edit-clear-formatting-accessibility-label" = "Fjern formatering";
"edit-comment-accessibility-label" = "Tilføj kommentarsyntaks";
"edit-comment-remove-accessibility-label" = "Fjern kommentarsyntaks";
"edit-decrease-indent-depth-accessibility-label" = "Formindsk indrykningsdybden";
"edit-direction-down-accessibility-label" = "Flyt markøren ned";
"edit-direction-left-accessibility-label" = "Flyt markøren til venstre";
"edit-direction-right-accessibility-label" = "Flyt markøren til højre";
"edit-direction-up-accessibility-label" = "Flyt markøren op";
"edit-increase-indent-depth-accessibility-label" = "Øg indrykningsdybden";
"edit-link-display-text-title" = "Vis tekst";
"edit-link-remove-link-title" = "Fjern link";
"edit-link-title" = "Rediger link";
"edit-menu-item" = "Rediger";
"edit-minor-learn-more-text" = "Lær mere om mindre ændringer";
"edit-minor-text" = "Dette er en mindre ændring";
@@ -315,6 +326,7 @@
"field-token-title" = "Bekræftelseskode";
"field-username-placeholder" = "indtast brugernavn";
"field-username-title" = "Brugernavn";
"filter-options-all" = "Alle";
"find-infolabel-number-matches" = "$1 / $2";
"find-replace-header" = "Find og erstat";
"find-textfield-accessibility" = "Find";
@@ -397,6 +409,37 @@
"no-internet-connection" = "Ingen internetforbindelse";
"no-internet-connection-article-reload-button" = "Vend tilbage til seneste gemte version";
"notifications-center-feed-news-notification-button-text" = "Slå push-notifikationer til";
"notifications-center-filters-read-status-item-title-all" = "Alle";
"notifications-center-filters-read-status-item-title-read" = "Læst";
"notifications-center-filters-read-status-item-title-unread" = "Ulæst";
"notifications-center-filters-read-status-section-title" = "Læsestatus";
"notifications-center-filters-title" = "Filtre";
"notifications-center-filters-types-item-title-all" = "Alle typer";
"notifications-center-go-to-article" = "Artikel";
"notifications-center-go-to-article-talk-format" = "$1 diskussionsside";
"notifications-center-go-to-diff" = "Forskel";
"notifications-center-go-to-talk-page" = "Diskussionsside";
"notifications-center-go-to-user-page" = "$1's brugerside";
"notifications-center-go-to-wikidata-item" = "Wikidata-emne";
"notifications-center-go-to-your-talk-page" = "Din diskussionsside";
"notifications-center-header-alert-from-agent" = "Advarsel fra $1";
"notifications-center-inbox-title" = "Projekter";
"notifications-center-inbox-wikimedia-projects-section-footer" = "Kun projekter, du har oprettet en konto til, vises her";
"notifications-center-inbox-wikimedia-projects-section-title" = "Wikimedia-projekter";
"notifications-center-inbox-wikipedias-section-title" = "Wikipedias";
"notifications-center-language-project-name-format" = "$1 $2";
"notifications-center-mark" = "Markér";
"notifications-center-mark-all-as-read" = "Markér alle som læste";
"notifications-center-mark-as-read" = "Markér som læst";
"notifications-center-mark-as-unread" = "Markér som ulæst";
"notifications-center-more-action-accessibility-label" = "Mere";
"notifications-center-onboarding-modal-continue-action" = "Fortsæt";
"notifications-center-status-all" = "Alle";
"notifications-center-status-all-notifications" = "Alle meddelelser";
"notifications-center-subheader-welcome" = "Velkommen!";
"notifications-center-swipe-more" = "Mere";
"notifications-center-type-title-welcome" = "Velkommen";
"notifications-push-fallback-body-text" = "Ny aktivitet på Wikipedia";
"number-billions" = "$1 mia.";
"number-millions" = "$1 mio.";
"number-thousands" = "$1K";
@@ -451,6 +494,17 @@
"potd-widget-title" = "Dagens billede";
"preference-summary-eventlogging-opt-in" = "Tillad Wikimedia Foundation at indsamle oplysninger om, hvordan du bruger appen, for at gøre appen bedre.";
"preference-title-eventlogging-opt-in" = "Send anvendelsesrapporter";
"project-name-mediawiki" = "MediaWiki";
"project-name-wikibooks" = "Wikibooks";
"project-name-wikidata" = "Wikidata";
"project-name-wikimedia-commons" = "Wikimedia Commons";
"project-name-wikinews" = "Wikinews";
"project-name-wikiquote" = "Wikiquote";
"project-name-wikisource" = "Wikisource";
"project-name-wikispecies" = "Wikispecies";
"project-name-wikiversity" = "Wikiversity";
"project-name-wikivoyage" = "Wikivoyage";
"project-name-wiktionary" = "Wiktionary";
"reading-list-add-generic-hint-title" = "Tilføj denne artikel til en læseliste?";
"reading-list-add-hint-title" = "Tilføj »$1« til læselisten?";
"reading-list-add-saved-button-title" = "Ja, tilføj dem til mine læselister";
@@ -511,7 +565,9 @@
"reading-themes-controls-syntax-highlighting" = "Syntaksfremhævelse";
"reference-title" = "Reference $1";
"relative-date-days-ago" = "{{PLURAL:$1|0=I dag|1=I går|$1 dage siden}}";
"relative-date-hours-abbreviated" = "$1 time";
"relative-date-hours-ago" = "{{PLURAL:$1|0=For nyligt|$1 time siden|$1 timer siden}}";
"relative-date-hours-ago-abbreviated" = "$1 timer siden";
"relative-date-minutes-ago" = "{{PLURAL:$1|0=Lige nu|$1 minut siden|$1 minutter siden}}";
"relative-date-years-ago" = "{{PLURAL:$1|0=Dette år|1=Sidste år|$1 år siden}}";
"replace-buttons-replace-all-accessibility" = "Erstat alle instanser";
@@ -603,6 +659,10 @@
"two-factor-login-with-regular-code" = "Brug bekræftelseskode";
"unknown-generic-text" = "Ukendt";
"variants-alert-dismiss-button" = "Nej tak";
"watchlist-user-button-thank" = "Tak";
"watchlist-user-button-user-contributions" = "Brugerbidrag";
"watchlist-user-button-user-page" = "Brugerside";
"watchlist-user-button-user-talk-page" = "Brugers diskussionsside";
"welcome-exploration-explore-feed-description" = "Anbefalet læsning og daglige artikler fra vores fællesskab";
"welcome-exploration-explore-feed-title" = "Udforsk nyhedskilde";
"welcome-exploration-on-this-day-description" = "Rejs tilbage i tid for at lære noget om hvad der skete i dag historisk";
130 changes: 130 additions & 0 deletions Wikipedia/Localizations/pt.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -149,6 +149,7 @@
"article-languages-label" = "Escolher língua";
"article-languages-others" = "Outras línguas";
"article-languages-yours" = "As suas línguas";
"article-nav-edit" = "Editar";
"article-read-more-title" = "Ler mais";
"article-reference-view-title" = "Referência $1";
"article-revision-history" = "Historial de revisões do artigo";
@@ -261,6 +262,32 @@
"diff-unedited-lines-format" = "{{PLURAL:$1|$1 linha não editada|$1 linhas não editadas}}";
"diff-user-button-accessibility-text" = "Toque duas vezes para abrir o menu";
"dim-images" = "Atenuar imagens";
"donate-accessibility-amount-button-hint" = "Premir duas vezes para selecionar o montante do donativo.";
"donate-accessibility-donate-hint-format" = "Premir duas vezes para fazer um donativo de $1 à Wikimedia Foundation.";
"donate-accessibility-email-opt-in-hint" = "Premir duas vezes para dar autorização à Wikimedia Foundation de lhe enviar correio eletrónico.";
"donate-accessibility-keyboard-done-hint" = "Premir duas vezes para fechar a vista do teclado de entrada do montante.";
"donate-accessibility-monthly-recurring-hint" = "Premir duas vezes para ativar donativos mensais automáticos deste montante.";
"donate-accessibility-textfield-hint" = "Inserir o montante personalizado do donativo.";
"donate-accessibility-transaction-fee-hint" = "Premir duas vezes para adicionar taxa da transação ao montante do donativo.";
"donate-already-donated" = "Obrigado, caro benfeitor! A sua generosidade ajuda a continuar o desenvolvimento da Wikipédia e dos \"sites\" associados.";
"donate-apple-fine-print" = "A Apple não é responsável por arrecadar fundos para este fim.";
"donate-email-opt-in-text" = "Sim, a Wikimedia Foundation pode enviar-me correio eletrónico ocasionalmente.";
"donate-help-frequently-asked-questions" = "Perguntas frequentes";
"donate-help-other-ways-to-give" = "Outras formas de contribuir";
"donate-help-problems-donating" = "Problemas no donativo?";
"donate-help-tax-deductibility-information" = "Informação sobre dedução fiscal";
"donate-later-title" = "Iremos lembrá-lo novamente amanhã.";
"donate-maximum-error-text" = "Não podemos aceitar donativos superiores a $1 $2 através do nosso ''site''. Entre em contacto com a nossa equipa de contribuições substanciais em benefactors@wikimedia.org, por favor.";
"donate-minimum-error-text" = "Selecionar um montante (mínimo $1 $2).";
"donate-monthly-recurring-text" = "Tornar este donativo em recorrente mensal.";
"donate-payment-method-prompt-apple-pay-button-title" = "Donativo com o Apple Pay";
"donate-payment-method-prompt-message" = "Fazer um donativo com o Apple Pay ou escolher outro método de pagamento.";
"donate-payment-method-prompt-other-button-title" = "Outro método de pagamento";
"donate-payment-method-prompt-title" = "Fazer donativo com o Apple Pay?";
"donate-success-subtitle" = "A sua generosidade tem grande significado para nós.";
"donate-success-title" = "Obrigado!";
"donate-title" = "Selecionar um montante";
"donate-transaction-fee-opt-in-text" = "Acrescento generosamente $1 para cobrir as taxas de transação, de forma a receberem 100%% do meu donativo.";
"edit-bold-accessibility-label" = "Acrescentar formatação para negrito";
"edit-bold-remove-accessibility-label" = "Remover formatação de negrito";
"edit-clear-formatting-accessibility-label" = "Remover formatação";
@@ -912,6 +939,8 @@
"replace-replace-all-results-count" = "{{PLURAL:$1|$1 instância substituída|$1 instâncias substituídas}}";
"replace-textfield-accessibility" = "Substituir";
"replace-textfield-placeholder" = "Substituir por...";
"return-button-title" = "Voltar";
"return-to-article" = "Voltar ao artigo";
"reverted-edit-title" = "Edição revertida";
"saved-all-articles-title" = "Todos os artigos";
"saved-default-reading-list-tag" = "Esta lista não pode ser eliminada";
@@ -944,6 +973,7 @@
"settings-clear-cache-are-you-sure-title" = "Limpar dados da cache?";
"settings-clear-cache-cancel" = "Cancelar";
"settings-clear-cache-ok" = "Limpar cache";
"settings-donate" = "Fazer donativo";
"settings-help-and-feedback" = "Ajuda e comentários";
"settings-language-bar" = "Mostrar línguas na pesquisa";
"settings-languages-feed-customization" = "Pode gerir as línguas mostradas personalizando as configurações do seu feed Explorar.";
@@ -990,6 +1020,67 @@
"share-social-mention-format" = "“$1” via Wikipédia: $2";
"sort-by-recently-added-action" = "Adicionado recentemente";
"sort-by-title-action" = "Título";
"source-editor-accessibility-label-bold" = "Acrescentar formatação para negrito";
"source-editor-accessibility-label-bold-selected" = "Remover formatação de negrito";
"source-editor-accessibility-label-citation" = "Acrescentar sintaxe para referência";
"source-editor-accessibility-label-citation-selected" = "Remover sintaxe de referência";
"source-editor-accessibility-label-clear-formatting" = "Limpar formatação";
"source-editor-accessibility-label-close-header-select" = "Fechar menu de estilos de texto";
"source-editor-accessibility-label-close-main-input" = "Fechar menu de formatação de texto";
"source-editor-accessibility-label-comment" = "Acrescentar sintaxe para comentário";
"source-editor-accessibility-label-comment-selected" = "Remover sintaxe de comentário";
"source-editor-accessibility-label-cursor-down" = "Mover cursor para baixo";
"source-editor-accessibility-label-cursor-left" = "Mover cursor para a esquerda";
"source-editor-accessibility-label-cursor-right" = "Mover cursor para a direita";
"source-editor-accessibility-label-cursor-up" = "Mover cursor para cima";
"source-editor-accessibility-label-find" = "Localizar na página";
"source-editor-accessibility-label-find-button-clear" = "Limpar localizar";
"source-editor-accessibility-label-find-button-close" = "Fechar localizar";
"source-editor-accessibility-label-find-button-next" = "Próximo resultado localizado";
"source-editor-accessibility-label-find-button-prev" = "Resultado localizado anterior";
"source-editor-accessibility-label-find-text-field" = "Localizar";
"source-editor-accessibility-label-format-heading" = "Mostrar menu de estilos de texto";
"source-editor-accessibility-label-format-text" = "Mostrar menu de formatação de texto";
"source-editor-accessibility-label-format-text-show-more" = "Mostrar menu de formatação de texto";
"source-editor-accessibility-label-indent-decrease" = "Reduzir profundidade da indentação";
"source-editor-accessibility-label-indent-increase" = "Aumentar profundidade da indentação";
"source-editor-accessibility-label-italics" = "Acrescentar formatação para itálico";
"source-editor-accessibility-label-italics-selected" = "Remover formatação de itálico";
"source-editor-accessibility-label-link" = "Acrescentar sintaxe para hiperligação";
"source-editor-accessibility-label-link-selected" = "Remover sintaxe de hiperligação";
"source-editor-accessibility-label-media" = "Inserir multimédia";
"source-editor-accessibility-label-ordered" = "Tornar a linha corrente numa lista ordenada";
"source-editor-accessibility-label-ordered-selected" = "Remover lista ordenada da linha corrente";
"source-editor-accessibility-label-replace-button-clear" = "Limpar substituir";
"source-editor-accessibility-label-replace-button-perform-format" = "Executar a operação de substituição. O tipo de substituição está definido como $1";
"source-editor-accessibility-label-replace-button-switch-format" = "Alternar o tipo de substituição. O tipo atual é $1. Selecionar para alterar.";
"source-editor-accessibility-label-replace-text-field" = "Substituir";
"source-editor-accessibility-label-replace-type-all" = "Substituir todas as instâncias";
"source-editor-accessibility-label-replace-type-single" = "Substituir a instância";
"source-editor-accessibility-label-strikethrough" = "Acrescentar riscado";
"source-editor-accessibility-label-strikethrough-selected" = "Remover riscado";
"source-editor-accessibility-label-subscript" = "Acrescentar formatação para subscrito";
"source-editor-accessibility-label-subscript-selected" = "Remover formatação de subscrito";
"source-editor-accessibility-label-superscript" = "Acrescentar formatação para sobrescrito";
"source-editor-accessibility-label-superscript-selected" = "Remover formatação de sobrescrito";
"source-editor-accessibility-label-template" = "Acrescentar sintaxe para predefinição";
"source-editor-accessibility-label-template-selected" = "Remover sintaxe de predefinição";
"source-editor-accessibility-label-underline" = "Acrescentar sublinha";
"source-editor-accessibility-label-underline-selected" = "Remover sublinha";
"source-editor-accessibility-label-unordered" = "Tornar a linha corrente numa lista não ordenada";
"source-editor-accessibility-label-unordered-selected" = "Remover lista não ordenada da linha corrente";
"source-editor-clear-formatting" = "Limpar formatação";
"source-editor-find-replace-all" = "Substituir todas";
"source-editor-find-replace-single" = "Substituir";
"source-editor-find-replace-with" = "Substituir por...";
"source-editor-heading" = "Cabeçalho";
"source-editor-paragraph" = "Parágrafo";
"source-editor-style" = "Estilo";
"source-editor-subheading1" = "Subtítulo 1";
"source-editor-subheading2" = "Subtítulo 2";
"source-editor-subheading3" = "Subtítulo 3";
"source-editor-subheading4" = "Subtítulo 4";
"source-editor-text-formatting" = "Formatação de texto";
"table-of-contents-button-label" = "Índice";
"table-of-contents-close-accessibility-hint" = "Fechar";
"table-of-contents-close-accessibility-label" = "Fechar índice";
@@ -1145,6 +1236,45 @@
"watchlist-empty-view-filter-title" = "Não tem páginas vigiadas";
"watchlist-empty-view-subtitle" = "Acompanhe o que está a acontecer a artigos do seu interesse. Toque no menu do artigo e selecione “Vigiar” para ver cada alteração de um artigo.";
"watchlist-empty-view-title" = "Os artigos que adicionou às páginas vigiadas aparecem aqui";
"watchlist-expiration-subtitle" = "As páginas permanecem na lista por padrão, mas existem opções para vigilância temporária entre uma semana a um ano.";
"watchlist-expiration-title" = "Definir expiração";
"watchlist-filter" = "Filtrar";
"watchlist-filter-activity-header" = "Atividade das páginas vigiadas";
"watchlist-filter-activity-options-seen-changes" = "Mudanças já vistas";
"watchlist-filter-activity-options-unseen-changes" = "Mudanças ainda não vistas";
"watchlist-filter-automated-contributions-header" = "Contribuições automatizadas";
"watchlist-filter-automated-contributions-options-bot" = "Robô";
"watchlist-filter-automated-contributions-options-human" = "Ser humano (não robô)";
"watchlist-filter-latest-revisions-header" = "Últimas revisões";
"watchlist-filter-latest-revisions-options-latest-revision" = "Última revisão";
"watchlist-filter-latest-revisions-options-not-latest-revision" = "Exceto a última revisão";
"watchlist-filter-significance-header" = "Importância";
"watchlist-filter-significance-options-minor-edits" = "Edições menores";
"watchlist-filter-significance-options-non-minor-edits" = "Edições não-menores";
"watchlist-filter-type-of-change-header" = "Tipo de mudança";
"watchlist-filter-type-of-change-options-category-changes" = "Alterações de categoria";
"watchlist-filter-type-of-change-options-logged-actions" = "Operações registadas";
"watchlist-filter-type-of-change-options-page-creations" = "Criações de páginas";
"watchlist-filter-type-of-change-options-page-edits" = "Edições de páginas";
"watchlist-filter-type-of-change-options-wikidata-edits" = "Edições de Wikidata";
"watchlist-filter-user-registration-header" = "Registo e experiência dos utilizadores";
"watchlist-filter-user-registration-options-registered" = "Registados";
"watchlist-filter-user-registration-options-unregistered" = "Não registados";
"watchlist-number-filters" = "Modificar [{{PLURAL:$1|$1 filtro|$1 filtros}}](wikipedia://watchlist/filter) para ver mais elementos da lista de páginas vigiadas";
"watchlist-onboarding-button-title" = "Saber mais sobre a lista de páginas vigiadas";
"watchlist-onboarding-title" = "Apresentação da lista de páginas vigiadas";
"watchlist-removed" = "Removida das suas páginas vigiadas";
"watchlist-thanks-success" = "O seu agradecimento foi enviado a $1";
"watchlist-track-subtitle" = "A lista de páginas vigiadas é uma ferramenta que lhe permite acompanhar as mudanças feitas nas páginas ou artigos do seu interesse.";
"watchlist-track-title" = "Monitorização de mudanças";
"watchlist-updates-subtitle" = "A lista das páginas vigiadas que adicionou, como edições ou discussões, pode ser acedida em Definições → Conta.";
"watchlist-updates-title" = "Ver atualizações";
"watchlist-user-button-thank" = "Agradecer";
"watchlist-user-button-user-contributions" = "Contribuições do utilizador";
"watchlist-user-button-user-page" = "Página de utilizador";
"watchlist-user-button-user-talk-page" = "Página de discussão do utilizador";
"watchlist-watch-subtitle" = "Premindo a estrela ou a operação \"Vigiar\" na barra de ferramentas inferior de um artigo, pode adicionar essa página à sua lista de páginas vigiadas.";
"watchlist-watch-title" = "Vigiar artigos";
"welcome-exploration-explore-feed-description" = "Leitura recomendada e artigos diários da nossa comunidade";
"welcome-exploration-explore-feed-title" = "O feed \"Explorar\"";
"welcome-exploration-on-this-day-description" = "Retroceda no tempo para saber o que aconteceu hoje na história";
1 change: 1 addition & 0 deletions Wikipedia/Localizations/th.lproj/Localizable.strings
Original file line number Diff line number Diff line change
@@ -829,6 +829,7 @@
"vanish-account-learn-more-text" = "เรียนรู้เพิ่มเติม";
"vanish-account-warning-title" = "คำเตือน";
"variants-alert-dismiss-button" = "ไม่ขอบคุณ";
"watchlist-user-button-user-page" = "หน้าผู้ใช้";
"welcome-exploration-explore-feed-description" = "บทความแนะนำประจำวันจากชุมชนของเรา";
"welcome-exploration-explore-feed-title" = "ฟีดสำรวจ";
"welcome-exploration-on-this-day-description" = "ย้อนเวลากลับไปเพื่อเรียนรู้ว่าเกิดอะไรขึ้นในเวลาที่ผ่านมา";
Binary file modified Wikipedia/iOS Native Localizations/da.lproj/Localizable.strings
Binary file not shown.
Binary file modified Wikipedia/iOS Native Localizations/pt.lproj/Localizable.strings
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -813,5 +813,21 @@
<string>%1$d bytes</string>
</dict>
</dict>
<key>watchlist-number-filters</key>
<dict>
<key>NSStringLocalizedFormatKey</key>
<string>Modificar [%#@v1@](wikipedia://watchlist/filter) para ver mais elementos da lista de páginas vigiadas</string>
<key>v1</key>
<dict>
<key>NSStringFormatSpecTypeKey</key>
<string>NSStringPluralRuleType</string>
<key>NSStringFormatValueTypeKey</key>
<string>d</string>
<key>one</key>
<string>%1$d filtro</string>
<key>other</key>
<string>%1$d filtros</string>
</dict>
</dict>
</dict>
</plist>
Binary file modified Wikipedia/iOS Native Localizations/th.lproj/Localizable.strings
Binary file not shown.

0 comments on commit 48a1c7b

Please sign in to comment.